Skip to content
OCTANE LABS
How We Run Claude Code on Production Rails Work

How We Run Claude Code on Production Rails Work

Evan Sparkman · October 19, 2026 · 8 min read

How our engineers direct Claude Code on production Rails work: what our open-source harness checks for you, and the rules and hooks we add ourselves.

Our engineers decide what gets built and own what ships, on client work and on the products we run ourselves. Claude Code is a tool we direct, and set up right it makes those engineers faster. Without controls, it forgets a rule you gave it an hour ago. It’ll also tell you something is done before it has run a single test.

So we run it inside two layers. They’re what let us move faster and still hold the same bar for what ships. The first is harness, a plugin we publish as open source — install it and you get the same gates we use. The second is our own config in ~/.claude: settings, hooks, slash commands, and a rulebook. Harness doesn’t include any of that, so if you want it you’d copy it by hand.

The rulebook and the banner

Every session starts by loading our global CLAUDE.md. Part of it is engineering standards like migration constraints, testing rules, Rails 8 defaults, and commit format. The rest covers how Claude should work: when to delegate, and when something counts as finished.

A model will skim a long rules file and then drift from it. So the rulebook has Claude print a banner before anything else:

🟢 Global CLAUDE.md loaded.
   Stack: Rails
   Delegation: ENFORCED
   Agents: rails-architect, rails-model-engineer, rails-testing-expert, ...
   Test framework: minitest
   Runtime: Ruby 4.0.6 | Framework: Rails 8.1.3.1

If the banner is missing, the rules didn’t load, and we know before any code gets written. It also puts Claude on record. Once it has printed “Delegation: ENFORCED”, writing code itself means breaking a rule it stated a minute ago.

The model prints that banner, though, so the model could fake it. Harness adds a second check that a SessionStart hook prints, headed HARNESS CHECK (SessionStart hook — environment-verified, not model memory). The rulebook and the 🟢 banner are ours. The hook-printed check comes with harness.

A specialist agent for each kind of change

The main session doesn’t write application code. Our rulebook has it hand every change to one of twenty-two specialist sub-agents — models and migrations, controllers, Hotwire, tests, security and performance, deploys, and more.

Each specialist has a short prompt about one job. A model engineer that only thinks about migrations is a lot more likely to remember null: false and foreign_key: true than a single agent trying to keep a whole feature in its head.

The agents are in rails-agents. Harness has a /harness:agents command that brings them into a project’s .claude/agents/. By default it symlinks them, so one git pull updates every project. --copy commits copies into the project instead. The command is harness. The rule that says the main session has to delegate is ours.

Checklists at the point of failure

Always-on rules get skimmed. A short checklist that shows up right when a known mistake is about to happen works better. Harness ships eight of these playbooks: CODE, DEBUG, VERIFY, TRAPS, RUNTIME, MECHANISM, PRECEDENT, and SCOPE. Claude goes through the items by ID and marks each one PASS, FAIL, or N/A with evidence.

Our rulebook routes six of them:

The moment Claude… It reads
makes its first edit to a file CODE.md
touches dates, time zones, money, sorting, regex, or async code TRAPS.md
sees a failing command or a traceback DEBUG.md
is about to say “done” or open a PR VERIFY.md
claims a code path is live in production, or sizes its impact RUNTIME.md
concludes code is dead, or plans around how an outside API behaves MECHANISM.md

When a row matches, Claude writes TRIGGER: <event> -> <doc> and reads the doc. The routing table and the TRIGGER line are ours. The playbooks are harness.

TRAPS.md covers DST-safe date math, float money, negative modulo (Ruby floors where JavaScript truncates), mutation versus copy, and closures in loops. Those get through code review and break in production. The last two rows cover a different kind of mistake. We’ve had Claude confidently explain how part of our code worked, and then we read the code and it did something else.

Hard limits

Some rules we don’t leave to the model’s judgment. Everything in this section is ours, and most of it runs as hooks — scripts Claude Code runs before a tool call that can block it.

  • No edits on the default branch. A PreToolUse hook on Edit and Write checks the branch of the repo the file lives in. If it’s main or master, the edit is denied and Claude has to branch first. We left out a per-call bypass on purpose.

  • No AI attribution in commits or PRs. Commit history belongs to the client and the team. Claude Code’s built-in attribution setting is empty, and a hook scans git commit and gh pr create commands for an inline “Co-Authored-By: Claude” or “Generated with Claude Code” line and blocks it. It doesn’t scan a message read from a file with -F.

  • Production deploys go through a release script. A Kamal pre-build hook won’t build without a release version (APP_VERSION).

  • Shell commands run in a sandbox. Network access is allowlisted to package registries, GitHub, and a few named hosts. Sandboxed commands can’t read credential files like ~/.ssh and ~/.aws/credentials. git, gh, kamal, ssh, and docker run outside the sandbox.

What counts as done

Harness runs two gates as hooks. The verification gate is a Stop hook. When there are uncommitted code changes, it re-runs the project’s unit test command (bin/rails test for us) and keeps the turn open until it’s green. The review gate blocks the turn unless a review file matches the current diff with a passing verdict. Our reviewer is an agent modeled on DHH-style Rails review.

A green unit run still isn’t the bar for us. We’ve shipped a feature where every test passed and the deploy looked healthy. Prod was empty, because the data only ever got seeded in development.

So the rest of this is in our rulebook, and to be clear, no hook checks it:

  • Claude can only say done, fixed, or passing next to fresh command output from the same turn. Otherwise it reports EDITED-UNVERIFIED. “Should work” is banned. It writes either Verified: <command> -> <result> or UNVERIFIED — to confirm, run: <command>. Harness’s VERIFY playbook uses the same wording.

  • Nothing ships until the full bin/ci passes. That’s RuboCop on the full tree, herb-lint for ERB, bundler-audit, importmap audit, Brakeman, unit tests, system tests, and a seed replant. A green run stamps the signoff status.

  • When the review comes back with a Critical finding, the fix goes back to the specialist that wrote the code, and the review runs again.

  • Every feature gets at least one test that walks the user’s whole journey: visit, click, fill in, submit, check the result. Harness only warns about these.

Since adopting the harness on September 24, the reviewer has blocked 11 of 103 reviewed pull requests in our main app on a Critical finding before merge. It asked for changes on 2 more over our code-comment rules. Treat those numbers as a floor. Reviews that ran locally and were never posted to the PR aren’t counted.

Notes in a vault

Claude’s built-in memory is fine for working state. Anything a teammate or a later session needs goes into an Obsidian vault with one folder per project — decisions, investigations, runbooks, RCAs, and dated session notes.

Three slash commands of our own keep it in use. /session-start loads the latest session note and recent decisions. /remember saves a decision, and /session-end writes a recap with next steps. We count an investigation as finished once its findings are in the vault, since that’s what the next session reads.

Our policy is a short RCA for every feature, including the ones that ship clean. It asks what went wrong before it shipped and what caught it, then what guard would stop it next time. When the same class of mistake shows up twice, the fix has to be something structural, like a test or a hook, instead of a note to be more careful.

We’ve written 244 root-cause analyses across our projects since late July. A “flaky” test that turned out to be two test runs clobbering the same database went into one of them. In plenty of them, the honest answer to “what caught it” was a person, and none of the gates had flagged it.

Separate profiles for separate work

Separate work contexts get separate Claude Code config directories. Each has its own rulebook, memory, MCP servers, and secrets. A shell alias switches between them (CLAUDE_CONFIG_DIR=~/.claude-work claude). Both profiles can reach the same filesystem, but a session never auto-loads the other profile’s rulebook or memory, and it doesn’t pick up the other profile’s MCP servers either. This one is ours too.

What it costs

This is slower per change than running Claude Code with no controls. Handing work to specialists and running full CI both take time and tokens, and the review loop adds more. For a throwaway script it’s overkill, and we skip it there.

The gates are blunt too. Harness’s review gate keeps blocking while unrelated work sits paused and uncommitted in the tree.

For production code we’d pay that cost every time. A missing foreign key is cheap to catch today. So is a save that fails without ever surfacing an error, or a bug someone called fixed without reproducing it. A month later in production, they cost a lot more.

Try it

Install harness and you get:

  • the SessionStart status banner

  • the Stop verification gate and the review gate

  • a PreToolUse pipeline gate for Definition-of-Ready stories

  • the eight guardrail playbooks

  • a story-writer and product-manager pipeline that works with any stack

  • /harness:agents and /harness:doctor

The story lint needs Ruby. In auto-bootstrap, the gates default to warn. Our Rails agents are in rails-agents.

Our own config, which harness doesn’t include and you’re welcome to copy:

  • the CLAUDE.md rulebook, the 🟢 banner, the delegation rule, and the TRIGGER routing table

  • the branch guard, the attribution hook, the Kamal release check, and the sandbox

  • bin/ci, the evidence wording, fix routing, and journey tests

  • the vault and its slash commands

  • the RCA policy

  • separate config profiles

If you’d like help setting this up for your team, or want our engineers to build your product with this setup, tell us what you’re building.

About the author

Evan Sparkman

Founder & Lead Engineer

Evan founded Octane Labs after a decade building and rescuing Rails applications for startups and growing teams. He leads every engagement hands-on, from the first architecture review to the last deploy.

Have a project in mind?

Tell us about it and we'll walk through how we'd approach it.