Course · Codex · Subagents
Git worktrees for conflict-free parallel edits on the money hardening
The audit fanned out safely because every worker only read. The hardening changes files, and it changes them through this chapter’s constructed projection - so before any command runs, the label deserves its full statement.
Here it is: the audit’s findings list holds one violation and one coverage gap, because that is all this repo has. Ten files, two thousand tokens - a serial fix in your main thread would be the honest choice. We run the parallel procedure anyway, as the labeled projection: the same findings list at the size where it has a fix per path, three workers, three branches. The parallelism is projected. The edits that land through it are real.
The failure you’re actually preventing
Section titled “The failure you’re actually preventing”Picture two workers in one directory. Worker A is halfway through rewriting app/importers/csv.py - the file saved mid-edit, one function converted, the next still on the float. Worker B, whose slice touches the route that calls the importer in app/main.py, reads that file right now and reasons over a half-written state that never existed in any commit and never will. No conflict marker fires. B simply believes what it read.
The result is a non-deterministic, unreproducible failure - a test that breaks somewhere downstream, later, with a diff that no longer matches what is on disk. It costs you exactly the diagnostic turns this course keeps counting, chasing a bug that is not where the evidence points. Most explanations of worktrees lead with merge conflicts. The sharper hook is belief: without isolation, an agent reads its sibling’s half-saved file and believes it.
Agents make this worse than humans do. They do not pause because something looks off; they auto-iterate on the bad state, and they cannot attribute a passing test to the right branch. Humans trip over the same problem - they just mutter about it first.
A git worktree fixes exactly this. One repository, multiple checked-out working directories - each with its own files on disk, its own index, its own branch - all sharing the same object database and history. Three guarantees: file isolation (B cannot see A’s unsaved edits), index isolation (each has its own staging area), and a branch lock (git refuses to check out a branch already checked out elsewhere).
And it is cheap. git worktree add returns near-instantly because nothing is re-downloaded; the object database is shared. A clone, by contrast, re-fetches everything and hides each worker’s commits until a push.
Use a managed worktree when available; otherwise point Codex at a git worktree
Section titled “Use a managed worktree when available; otherwise point Codex at a git worktree”Some surfaces manage this for you: the app exposes a /worktree command that runs the chat in a new Git worktree, and where you have that, an isolated working context is one command away. The CLI workflow below keeps the mechanics explicit instead - because a worktree is just a directory Git made, and the integration point is “run this agent with that working directory.”
The projection’s three slices, on real files:
money/parse-app/importers/csv.py: the fix. Replace the float parse with a float-free conversion, per the parse-path rule the rules chapter proposed.money/wire-app/main.py+app/services/categorise.py: the cleared path. Verify that amounts pass through the route and the categorizer untouched; expected to produce no diff.money/tests- the suite undertests/: the pin. Add the regression test for the truncation case the audit flagged as a coverage gap.
# one worktree + branch per slice of the projectiongit worktree add ../budgetcli-parse -b money/parsegit worktree add ../budgetcli-wire -b money/wiregit worktree add ../budgetcli-tests -b money/testsThree worktrees, three branches - hold that count. The merge tail two lessons from now is priced by exactly this number, not by how many workers produced it.
One honesty note about that split. In ordinary practice a fix and its test land together on one branch; here they are separated so the merge order and the review gate have real work to do - the pin asserts the parse, so the two branches are related and ordered. That dependency is the projection’s one deliberate exaggeration, and the orchestration lesson pays it off.
Then each worker runs against its own directory:
codex --cd ../budgetcli-parse "convert the amount parse in app/importers/csv.pyto integer cents without a float, per the parse-path rule in AGENTS.md;keep the suite green"The flag is --cd, short form -C: set the working directory for the agent before it starts processing your request, per the Codex CLI reference. The placement matters twice - the worker’s edits land on that branch and nowhere else, and its sandbox fence is drawn around that directory, so a workspace-write worker can write its own tree and not the others.
The durable principle underneath: whatever isolation you build - worktree, separate clone, container - it reduces to spawn the agent with this working directory. Codex’s stance is “use plain git, point us at the path,” and that composes with everything you already run.
One convenience for the scripts: git -C <path> ... runs a git command inside a worktree without changing your shell’s directory, which keeps a fan-out script readable when it is juggling several. The workers need no help finding their own checkout - they were launched there.
The isolation a worktree does not give you
Section titled “The isolation a worktree does not give you”This is the part most teams learn the hard way, so learn it immediately after the win. A worktree isolates code state - files, index, branch pointer. It does nothing for execution state, and agents make that gap worse. Workers in three worktrees still share everything that lives outside their directories:
- Ports. Every worker that smoke-tests the API starts
uvicorn app.main:app; the first bindslocalhost:8000, the rest fail or - worse - pass against the wrong instance. Give each tree its own port. - Env and dotenv. A shared
.envat the repo root bleeds one worker’s configuration into another’s run. One.envper worktree, or none. - Shared caches and tool state - the virtualenv,
~/.cache, anything installed outside the tree - sits outside every worktree, and therefore outside every isolation.
Notice what is absent from that list: a database. The ledger in budgetcli (the course’s running example, a small self-hosted personal-finance API in Python and FastAPI) is in-memory - app/services/ledger.py holds a list - so today there is no shared schema to corrupt. The moment the repo grows a SQLite file or a data directory, that entry writes itself, and on a money app it is the entry that hurts most. The rule: anything that lives outside the working directory is not isolated by the worktree; you isolate it yourself.
And one distinction worth its own sentence, straight back to the approvals & sandbox chapter: runtime isolation is not security isolation. A worktree prevents accidental cross-contamination between cooperating agents. It does nothing against untrusted code, prompt injection, or a hostile worker - those belong to the kernel-level sandbox, network off, fence narrow, layered underneath. Worktrees keep honest workers from tripping over each other; they do not make a hostile one safe.
Clean up so they don’t pile up
Section titled “Clean up so they don’t pile up”Stale worktrees do not corrupt anything, but they accumulate working-file copies and eat disk. Two habits keep it tidy: git worktree remove when a slice has come home, and git worktree prune to clear registrations whose directories are already gone. In a scripted run, wrap teardown in a trap '...' EXIT so the trees are cleaned up even when a worker fails - guaranteed teardown is the difference between a runner that stays healthy and one that slowly fills with abandoned trees.
You now have three workers on the hardening, each walled off in its own tree, each producing a branch or a verdict - the fan-out, minus the cost the last lesson flagged and did not yet pay: everything still has to come home. That is the last lesson - light orchestration, where the merge tail stops being a claim and turns into counted turns.