Workflows · · 1,338 words · 6 min read
Git worktrees for coding agents: one checkout per session, why?
A worktree and branch per agent task, commits after every green, deny rules for destructive git, and a helper script exercised on a real repository.
git worktrees branches claude code codex
What you get here: the four version-control habits that keep an agent session recoverable, what a git worktree actually is and why it fits agents better than a second clone, how the Claude Code harness wraps worktrees and what it does on exit, the permission rules that keep the agent away from destructive git, and a helper script that I ran on a throwaway repository in this session.
The problem worktrees solve
An agent edits files in place. If it runs in your main checkout, then for the length of the session your working tree is shared between you and a process that makes dozens of edits a minute. You cannot switch branches, you cannot run your own tests without picking up half-finished changes, and if the session goes sideways, git status shows you a pile you did not make.
The git documentation describes the feature that fixes this: "A git repository can support multiple working trees, allowing you to check out more than one branch at a time. With git worktree add a new working tree is associated with the repository." The new tree is a "linked worktree" as opposed to the "main worktree" created by clone or init, and it shares "everything except per-worktree files such as HEAD, index, etc." Concretely, a linked worktree has a .git file rather than a .git directory, pointing at a private subdirectory under the main repository's .git/worktrees/. Objects, refs, and configuration are shared; the checked-out files and the index are not.
That sharing is why a worktree beats a second clone for agent work. The agent's commits land in the same object store, so reviewing its branch from your main checkout is one git diff, and there is no push and fetch between the two.
The four habits
One worktree and one branch per task. Not per session: per task. A task that turns into three tasks gets three branches, because each one needs to be reviewable and revertable on its own. The branch name carries the task name, so git branch is the task list.
Commit after every green. When the red-green gate passes, commit. Small commits are the checkpoints that survive a bad turn; the harness's own rewind covers only the edits it made through its file tools, and the Claude Code documentation is explicit that checkpoints are "not a replacement for git." Aider has done this since its early releases; its README at commit 5dc9490bb35f says it "automatically commits changes with sensible commit messages" so you can "use familiar git tools to easily diff, manage and undo AI changes." Whichever harness you use, the commit is the undo.
Keep the main checkout human-only. The agent lives in the worktree. You review, merge, and run the deploy from the main tree. If the agent's branch is unmergeable, you delete a directory and a branch and nothing you touched is affected.
Deny destructive git. The agent never needs git push --force, git reset --hard, git clean -f, or git checkout -- .. The permission rules below take them off the table rather than relying on the agent's judgement.
What the harness does with worktrees
Claude Code wraps the git command. Its worktrees page says that claude --worktree feature-auth creates the worktree "under .claude/worktrees/<name>/ at your repository root, on a new branch named worktree-<name>", and that running the command with a different name in another terminal starts "a second isolated session." The common-workflows page notes that a repository with no commits cannot do this and fails with a resolve error, so commit once before you begin.
Two details on that page matter for the habits above. On exit, the harness "checks the worktree for work that removal would delete: changed or untracked files, and new commits"; a clean unnamed session's worktree is removed automatically, and a worktree with work in it prompts you to keep or remove. Non-interactive runs with -p skip the prompt and leave the worktree in place, so scripted sessions need their own cleanup, which is what the clean subcommand below does.
The second detail is .worktreeinclude. A worktree is a fresh checkout, so gitignored files like .env are not in it. The page describes a .worktreeinclude file in .gitignore syntax where "only files that match a pattern and are also gitignored are copied, so tracked files are never duplicated." The shipped script reads the same file format.
The page also lists the isolation the harness enforces while a session is in a worktree: it blocks edits that target the main checkout, blocks commands whose working directory resolves to the main checkout, and blocks git invocations redirected into the main checkout through git -C, --git-dir, or a cd. Those are the checks that make "main checkout is human-only" a property of the harness rather than a hope.
Codex protects the repository from the other direction. Its approvals page says that in the default workspace-write sandbox the .git directory stays read-only along with .agents and .codex, so the agent can edit files but cannot rewrite history from inside the sandbox. Committing then requires an approval or a policy change, which is a reasonable default for a tool you have not learned to trust yet.
Permission rules for git
In Claude Code, rules go in .claude/settings.json and are evaluated "deny, then ask, then allow." The permissions page documents the prefix form Bash(git diff *) with the space before the asterisk, and warns that without the space the rule also matches git diff-index. Deny rules "block in every mode, including bypassPermissions." This is the set I use:
{
"permissions": {
"allow": [
"Bash(git status *)",
"Bash(git diff *)",
"Bash(git log *)",
"Bash(git add *)",
"Bash(git commit *)",
"Bash(git worktree list *)"
],
"ask": [
"Bash(git push *)",
"Bash(git rebase *)",
"Bash(git worktree remove *)"
],
"deny": [
"Bash(git push --force *)",
"Bash(git push -f *)",
"Bash(git reset --hard *)",
"Bash(git clean *)",
"Bash(git checkout -- *)",
"Bash(git branch -D *)"
]
}
}
One subtlety from the same page: approvals granted by choosing "Yes, and don't ask again" in a worktree session are saved "to the main checkout's .claude/settings.local.json", so they apply across every worktree of the repository. That is convenient and also means a careless approval in one session leaks into all of them; write the rules by hand instead.
The helper script
The shipped bash script has three subcommands. new <task> creates .worktrees/<task> on a branch agent/<task> from the current HEAD, adds .worktrees/ to .gitignore if it is missing, copies any gitignored paths listed in .worktreeinclude, and prints the commands to start an agent there. list shows every worktree with a clean or dirty flag. clean removes the worktrees that have no changes and keeps the ones that do, then prunes stale metadata.
I ran it in this session on a throwaway repository with one commit, a gitignored .env, and a .worktreeinclude naming it. Output is real; the long temporary paths are shortened to <repo>:
$ bash git-worktrees-branches-agent-sessions.sh new fix-parser
Preparing worktree (new branch 'agent/fix-parser')
HEAD is now at 6ac08a7 initial
seeded .env
worktree ready: <repo>/.worktrees/fix-parser (branch agent/fix-parser)
start an agent there with one of:
cd "<repo>/.worktrees/fix-parser" && claude
cd "<repo>/.worktrees/fix-parser" && codex --sandbox workspace-write
when it is green, review the diff from the main checkout:
git diff HEAD...agent/fix-parser
After editing a file inside fix-parser and creating a second, untouched worktree:
$ bash git-worktrees-branches-agent-sessions.sh list
dirty(1) main <repo>
clean agent/add-tests <repo>/.worktrees/add-tests
dirty(1) agent/fix-parser <repo>/.worktrees/fix-parser
$ bash git-worktrees-branches-agent-sessions.sh clean
removed <repo>/.worktrees/add-tests
kept <repo>/.worktrees/fix-parser (uncommitted changes or untracked files)
removed 1, kept 1; branches were left in place (delete with: git branch -d agent/<task>)
The main checkout shows as dirty in that listing because the script appended .worktrees/ to .gitignore and I had not committed it. That is the script telling the truth, and it is why clean only ever operates under .worktrees/.
The script does not delete branches, on purpose. A removed worktree with its branch intact is recoverable with git worktree add; a deleted branch with no worktree is only recoverable through the reflog, and the reflog is exactly the kind of thing an agent session makes noisy.
Where this does not help
Worktrees isolate files, not services. Two sessions that both start a dev server on the same port, or both write to the same local database, collide anyway. Worktrees also share the object store, so an agent that runs git gc --prune or rewrites refs from inside its worktree affects every tree; that is what the deny rules are for. And if your repository uses filter drivers from its own .git/config, the Claude Code page notes that worktree creation skips them, so LFS-backed files arrive as pointer files until you run git lfs pull in the worktree. None of these are reasons to skip the habit. They are the list of things to check when a session behaves strangely, before you blame the agent.
Code and data
- git-worktrees-branches-agent-sessions.sh — the complete listing used in this article.
Sources
- Git, "git-worktree Documentation" (read 2026-09-05)
- Anthropic, "Run parallel sessions with worktrees" (Claude Code docs, read 2026-09-05)
- Anthropic, "Common workflows" (Claude Code docs, read 2026-09-05)
- Anthropic, "Configure permissions" (Claude Code docs, read 2026-09-05)
- OpenAI, "Agent approvals & security" (Codex docs, read 2026-09-05)
- Aider-AI/aider, "README at commit 5dc9490bb35f (read 2026-09-05)"