I have up to three Claude agents writing Freebo code in parallel right now. Each one has its own git worktree, its own branch, its own port slot on the shared local stack. A coordinator tells them what to pick up. A verifier from the other model family grades their PRs. All of that is cool, and none of it is the reason the whole thing is safe.
The reason the whole thing is safe is a twelve-line array.
CLAUDE_DENY=(
"Bash(gh pr merge:*)" "Bash(gh pr review:*)" "Bash(gh pr ready:*)"
"Bash(git push origin main:*)" "Bash(git push origin HEAD:main:*)"
"Bash(git push origin *:production*)" "Bash(git push * production*)"
"Bash(railway up:*)" "Bash(railway down:*)" "Bash(railway redeploy:*)"
"Bash(railway variables --set:*)"
"Bash(railway deployment redeploy:*)" "Bash(railway deployment up:*)"
"mcp__plugin_railway_railway__redeploy"
"mcp__plugin_railway_railway__create-deployment"
"mcp__plugin_railway_railway__accept-deploy"
"mcp__plugin_railway_railway__set-variables"
"mcp__plugin_railway_railway__delete-service"
"mcp__plugin_railway_railway__restart-service"
)
That array lives in a 400-line bash script called fd. Every Claude session the script spawns — coordinator, PR owner, Gate A verifier — launches with those patterns passed to claude --disallowedTools. Deny rules hold in every Claude Code permission mode, including bypassPermissions. Nothing an agent does afterward can route around them.

The orchestration gets most of the credit when people see parallel agents working. It shouldn’t. The deny list is doing the load-bearing work.
Three clusters, three decisions I keep
Read the array again and you see three groups.
Merge verbs. gh pr merge, gh pr review, gh pr ready. An agent can push a branch, open a draft PR, request CI, iterate until green. It cannot mark its own PR ready, approve another agent’s PR, or merge anything. A human is the one who turns a green draft into something main sees.
Push-to-trunk verbs. git push origin main, git push origin HEAD:main, and two patterns that catch everything landing on the production branch. CI runs on push to main and Railway deploys from production, so these two branches are the only places an agent writing code on its own feature branch could accidentally make the world happen. Agents are welcome to force-push their own branch — with --force-with-lease — but they cannot name main or production as a destination.
Railway verbs. The whole deploy/redeploy/restart/set-variables surface of Railway, both the CLI commands and the MCP tool equivalents. Agents can read Railway. They cannot deploy, redeploy, set an env var in production, or restart a service.
An agent can do anything a junior engineer on their first day could do. What it can’t do is anything with the word “production” in the help text.
— The rule under all of it
That’s the whole mental model. The PR owner is a junior who has write access to feature branches. Nothing else. Everything that moves code toward customers is a verb I kept.
The second half of the trick, for the other family
I run cross-family verification — the agent that grades a PR is not from the same model family as the agent that wrote it. The practical implication: when the verifier is Codex instead of Claude, --disallowedTools doesn’t apply. Codex has no per-command deny list.
So the brief the verifier reads has a FORBIDDEN block, and the Codex sandbox keeps writes inside the workspace:
FORBIDDEN Editing, committing, pushing, rebasing, commenting on, approving,
marking ready or merging anything. Running gate-a.sh with --comment.
Touching production or staging. Pushing to the production branch.
Any railway command. "Probably fine." A verdict on a SHA
other than {{SHA7}}.
Two different enforcement mechanisms, same list. Claude gets a wire-level deny list from the harness. Codex gets a brief-level prohibition and a sandbox that doesn’t grant the capability in the first place. The verifier’s one job is to read, drive, and type a verdict. It literally cannot do more than that.
Two gates, and only one of them is for agents
There are two merge boundaries in the Freebo pipeline, and they do very different jobs.
Gate A is “is this PR safe to merge into main?” Agents run it. The Gate A verifier spins up the PR head on scratch ports, drives the lanes the diff touches, and posts a verdict on the commit SHA. A passing verdict is the signal — not the action. The agent cannot merge. I merge.
Gate B is “is main safe to promote to production?” Agents can run its read-only checks: PRs in the release, staging deployed at the right SHA, staging smoke lanes, the rollback target. The verdict is advisory. Only I push to production, which is the branch Railway actually deploys from.
The symmetry is intentional. An agent can collect all the evidence for both gates. An agent cannot complete either. That’s the whole point of the gates — they are the slots where the deny list bites.
The unsexy truth
If I showed you the Freebo agent setup and asked what makes it work, I think you’d guess the coordinator prompt, the Playwright lanes, the cross-family verification rule, or the parallel worktrees. All of those are real and they all matter. But the thing that lets me go to the gym while agents are shipping PRs is not any of that.
It’s that the agents cannot type gh pr merge.
Everything else is latency reduction. Agents build in parallel so I don’t have to wait. Verifiers come from the other family so I don’t have to catch every mistake personally. Lanes run live so a green verdict means a working app. All of that cuts the time I spend between “here is a goal” and “here is a merge-ready PR.”
But the deny list is the thing that lets the pipeline exist at all. Without it, autonomy is a liability. With it, autonomy is a scheduling convenience — the agent waits politely at the one place I need to be the one acting, and the worst thing it can do is open a PR that stays in draft forever.
If you are about to put an agent on your real codebase, write the array before you write the orchestrator. List every verb that, if executed by the wrong agent on the wrong day, would make you sick. Deny them. Then let the agent have everything else.
The orchestration is the fun part. The deny list is the part that ships.