The most expensive Claude Code mistakes I have made were not bad code generation. They were bad coordination. Agents stepping on the main session’s working tree, worktrees based on stale main quietly reverting eight commits of in-progress work, a permission flag set to “do not ask” that deleted a real client S3 bucket.
Each of those was a different failure, but they all had the same root cause: I had not written down what the safe shape of “delegate this to an agent” looked like. So I would improvise, and improvising at 11pm with three terminals open is how bad outcomes happen.
This rule is the version I have settled on after two incidents that I do not want to repeat.
The two incidents that shaped this rule
The first one cost a real client S3 bucket. I was running an agent with the permission flag set to “do not ask” because the task involved many small file uploads and I did not want to approve each one. Somewhere in the task plan, the model decided the bucket needed to be “cleaned up.” It ran aws s3 rb --force and the entire bucket was gone before any confirmation prompt would have surfaced. There was no soft delete, no recycle bin, no recovery. The bucket name was not the production bucket, but it was a working bucket a small team relied on.
The second one was subtler and took a week to fully understand. I was on a feature branch with eight commits of in-progress work. I spawned a worktree agent to add a small refactor to that feature. The agent did the refactor, committed, pushed, opened a PR. I merged the PR back into my feature branch. The merge brought in the refactor. It also brought in a complete reversion of my eight in-progress commits, because the worktree had been based on origin/main, not on my current HEAD. None of my prior commits existed in the worktree’s history, so merging the worktree’s branch (which started from main) reset my feature branch back to main’s tip.
Both incidents were preventable. Both incidents were caused by my own permission and isolation choices, not by the model doing something unexpected. The fix is to write down the choices, then never deviate.
The rule
Trimmed to the operational parts. White-labeled, mine sits at ~/.claude/rules/agent-safety.md:
# Agent Safety Rules
## Permission modes
- NEVER use `mode: "dontAsk"` or `mode: "bypassPermissions"` when
spawning agents.
## Worktree isolation
`WorktreeCreate` hook bases new agent worktrees on the parent
session's current HEAD, not `origin/<default-branch>` (binary
default overridden via hook).
Implications when `isolation: "worktree"`:
- Worktree branch = `worktree/<slug>`, base = parent HEAD at spawn
time. The parent branch is the target branch for the agent's PR.
- Agent CAN auto-commit and auto-push freely, MUST follow `git.md`
commit format (prefix, ticket ID from branch name).
- Worktree branch has NO upstream tracking (`--no-track`). First
push: `git push -u origin worktree/<slug>`. Subsequent: plain
`git push`.
- Agent CAN open a PR back into the target branch. Always PR,
never push directly to target, never auto-merge; user reviews
and merges.
- Brief the agent with the target branch name explicitly. Do not
let it infer from git state.
- After PR is opened, agent's job is done. Do not squash/delete
the worktree branch, user does that post-merge.
That is the part that decides what to do once you have decided to use a worktree. The harder question, and the one that took me the longest, is when to use a worktree at all.
The three placement options
Every “the model needs to edit some code” decision has three options. I labeled them A, B, C so I can pick fast.
Option A: stay in the main session. The main session does the edit itself, no sub-agent. Use only when all of these hold:
- Working tree clean (no uncommitted edits, no live
/tmp/scratch, no in-flight prod monitoring). - Edit lands on the branch the main session is already on.
- Trivial 1 or 2 file edit, nothing else in flight.
Option B: delegate to an agent without isolation. The agent edits the parent working directory directly, no worktree. Use when all of these hold:
- Edit lands on the branch the main session is already on.
- You want sub-agent specialization (a
fullstack-engineerpersona, aui-ux-designerpersona) or a longer-running task that should not block the main session synchronously. - The file scope is disjoint from any other concurrent edit. Main session is not editing the same files, no other no-isolation agent is touching the same files.
- No PR or review or CI gate is required for this work.
Option B has real risks. Multiple no-isolation agents on overlapping file scope means race conditions and last-writer-wins. If run_in_background: true, the main session is blind to the agent’s edits, so your in-memory view of the tree goes stale and you have to re-read files before further edits. There is no isolated rollback path; if the agent corrupts state, you revert via git like any other edit.
Option C: delegate to a worktree agent (isolation: "worktree", usually run_in_background: true). Use when any one of these holds:
- Main session is mid-debug. Tailing prod logs, holding
.env*edits, polling container state, copying scratch scripts, running monitoring loops. - Edit targets a different branch than the main session is on.
- Multiple unrelated hotfixes queued. One worktree per hotfix keeps them isolated.
- PR, review, or CI gate is required.
Option C is the default for anything non-trivial. The cost of a worktree is small (a directory and a branch). The cost of accidentally tangling a hotfix into your live debug session is large.
Why “current HEAD” matters
The worktree base branch problem is worth its own paragraph because it is the kind of bug that does not show up for a week and then nukes your work in one merge.
By default, Claude Code bases a worktree on origin/<default-branch>. That is reasonable if you imagine worktrees as short-lived feature factories spinning out clean PRs against main. In practice, my worktree agents almost always work in service of an in-progress feature branch, not in service of main. If they base on main, the merge back into the feature branch silently rewinds all the feature’s in-progress commits.
The fix is a WorktreeCreate hook that rewrites the worktree creation to use the parent’s current HEAD. Roughly:
git worktree add --no-track -b worktree/<slug> <path> HEAD
The --no-track is deliberate. The worktree’s branch should not silently track any upstream. The first push from the agent must use git push -u origin worktree/<slug> to set the upstream, after which subsequent pushes are plain git push.
The rule documents this once, so every agent I spawn inherits the right behavior. The hook is the enforcement. The rule is the explanation.
Integration without switching branches
This is the section the second incident drove. Once the worktree agent opens a PR, the main session has to merge it. The wrong way to merge is to checkout the agent’s branch. The right way is to never leave the main session’s current branch.
Three integration options that all avoid checkout:
- Local merge:
git fetch origin && git merge --ff-only origin/worktree/<slug>(or--no-fffor a merge commit). Use for personal or feature branches when no review or CI gate is required. - PR plus auto-merge: the agent opens a PR, CI runs, then
gh pr merge <PR#> --auto --squash. Use for shared branches (main,development,staging) or when review or CI is required. - Cherry-pick or patch:
git cherry-pick <sha>orgit format-patch -1 <sha> --stdout | git am. Use to skip or reorder commits, or to squash before merging.
The forbidden pattern is git checkout <target> && git merge <source> && git push. Switching branches mid-session resets the working tree, orphans scratch files, breaks monitoring loops. Worse, in a session where you are mid-debug on production, the working tree state itself is load-bearing. A git checkout mid-debug is not a navigation, it is a deletion of context.
There is a related incident worth naming. On a production deploy day, the main session was monitoring a live container, holding .env.production edits in working tree, copying scratch scripts to the server. Two unrelated Dockerfile and compose hotfixes came up. The main session did git checkout to each hotfix branch twice in a row, applied the fix, switched back. Each switch reset the working tree mid-debug, mixed scratch state across tasks, and obscured which .env edit belonged to which fix. The hotfixes should have been worktree agents. The rule above codifies that lesson.
Spawning patterns
The rule also enumerates the four spawning patterns and which one fits which case:
| Use case | Pattern |
|---|---|
| Independent edits, fast turnaround | Parallel plus foreground |
| Independent edits, main session must stay live | Parallel plus background |
| Multi-step where B depends on A | Sequential |
| One long task while main session continues | Single background agent |
| Two agents collaborating (write plus review) | Team plus SendMessage |
| Resume an agent later in the session | Team (one-shot cannot resume) |
This is a lookup table, not a flowchart. I read it when I am about to spawn something and pick the row that matches. Picking deliberately is faster than improvising every time.
When the rule does not apply
The whole rule is scoped to “main session spawns agents.” Direct work in the main session (no agents) follows normal git workflow. Switching branches in the main session for your own work is fine. The rule only governs how to behave when delegating.
There is also an exception for the dontAsk permission ban: I have never found a case worth the risk. Other Claude Code users in less destructive contexts (read-only data analysis, sandboxed environments) may find dontAsk acceptable. For any task that touches a remote system, a filesystem outside the worktree, or a cloud account, I would not relax this rule even once.
Variants you might want
- Per-project agent permissions. A stricter version of this rule pins per-project: which agents can run, what tools they can use, what paths they can write to. I keep mine global because I want the same baseline everywhere.
- Team rules vs individual rules. If you share a Claude Code config with a team, write the rule in two layers: a strict team-level rule that defines what is forbidden, plus a personal layer that adds your preferences. The team layer should be the floor.
- Different integration default. If you do not care about audit trail and CI for any of your branches, you can swap “PR plus auto-merge” for “local merge” as the default. I prefer the audit trail for any shared branch.
- Sub-agent persona allowlist. A nice extension is a list of allowed
subagent_typevalues. If your project only uses two subagents (fullstack-engineer,ui-ux-designer), enumerate them in the rule and treat anything else as a typo.
Closing note
This rule is about three hundred lines if you include all the prose, and most of those lines exist because of a specific past failure. None of them are speculation about what might go wrong. Every section maps to “this exact thing went wrong, here is the rule that prevents the next time.”
For the underlying mechanics, the spawning patterns and worktree isolation posts in the claude-code from zero series cover the binary’s behavior. The worktree hotfix during production debug post is the live example of the production-day pattern this rule was written to prevent.