The Bare Commit That Swept Another Session’s Work
Two agent sessions shared one checkout on one branch. A bare git commit in one session committed whatever the other had staged at that instant. Every individual command was correct. The hazard was structural.
A main checkout has exactly one git index and one HEAD. Run two agent sessions inside it and both sessions share both. In early June that stopped being theoretical: one session ran a bare `git commit` with no pathspec, and it committed whatever was staged at that instant, including files the sibling session had staged for a completely different change. Later, a reset moved the branch out from under work the other session had already pushed. No command misbehaved. The structure did.
The day after, the third face of the same bug showed up, and it was the one that actually cost work. A session had made an edit and was partway through verifying it, a grep and a build, when sibling sessions moved the shared HEAD through several branches as a pull request merged and its branch was deleted. The uncommitted edit was silently discarded. Not corrupted, not conflicted: gone, with nothing to warn anyone. The fix had to be recreated from scratch in an isolated worktree.
That reframed the hazard for me. It is not the commit that is dangerous. It is the gap between edit and commit. An uncommitted edit lives only on disk, and on a shared checkout, any sibling's branch switch or reset can move the working tree out from under it. Every multi-step verification you run while an edit sits uncommitted is time spent holding the work inside the danger window.
The rules that came out of it: never run a bare commit in a shared checkout, scope every commit with an explicit pathspec so a concurrent session's staging cannot be swept in. Commit each logical edit before any multi-step verification, not after. And the structural fix that makes the whole class impossible: work in a linked worktree with its own index and HEAD from the start.
Then the epilogue that kept it honest. The guards exempted linked worktrees, on the theory that each has a single owner. A week later two sessions ended up co-tenants of the same linked worktree, and one session's bare commits swept the sibling's uncommitted edits, the identical failure one level down. A linked worktree also has exactly one index and one HEAD. The exemption had encoded an assumption as an invariant. The guards now count co-tenants on any shared tree, whatever kind it is.
Enforcement lives in three places, because a rule without a tripwire is a suggestion: a session-boundary check that flags co-tenancy, a pre-commit hook that prints exactly which files a commit will write when the checkout is shared, and a per-edit nudge the moment a write targets a co-tenanted tree. None of them block. All of them make the invisible thing visible right before it matters.
# Two sessions, one checkout, one index. # Session A stages its change: git add lib/feature-a.ts # Session B, same instant, commits "its" work: git commit -m "fix b" # <- bare commit sweeps A's staged file # The rule: git commit lib/feature-b.ts -m "fix b" # pathspec, always # The structural fix: git worktree add .claude/worktrees/fix-b # own index, own HEAD # One session, one worktree. The race stops existing.