Two coding agents, two windows, one repository. They share one staging area, and the rule telling them to be careful is a paragraph on a crowded screen.
The fix is a check that can refuse, and the hook is a few lines of shell. The hard part is one answer git does not record: whose work is this staged path.
One index, and a plain git commit takes all of it
Sessions sharing one checkout share one git index: global mutable state with no owner. A
git add here and a git add next door produce one staged set. A git commit with no pathspec
takes every bit of it, and neither session is told. Git has one index per working tree, and it is a
file both sessions write to. It is what happens
the third or fourth time you open a session in the repository you are already in.
A written rule is what a busy session skips. Ours broke three times in a week. Twice in late August
2026, a plain git commit swept in files another session had staged. Six days later a bare
git reset --hard HEAD destroyed another line’s in-progress merge: a MERGE_HEAD, six
hand-resolved conflicts, one new file of 2,265 lines.
All three times the rule existed, the check was run, the output was correct, and it was read past.
Nothing was missing. An instruction is advice until something can enforce it.
Write the half that depends on nothing, because it can refuse
If MERGE_HEAD, CHERRY_PICK_HEAD, REVERT_HEAD or a rebase state directory exists in the git
directory, your git commit is about to conclude somebody’s operation. In a shared checkout, that
somebody may not be you. Nine lines, no infrastructure:
#!/bin/sh
# pre-commit — refuse to conclude an operation this session may not have started.
git_dir=$(git rev-parse --git-dir) || exit 0
for f in MERGE_HEAD CHERRY_PICK_HEAD REVERT_HEAD rebase-merge rebase-apply; do
[ -e "$git_dir/$f" ] || continue
echo "pre-commit: $f exists — this commit concludes an operation in progress." >&2
echo " If it is yours: git commit --no-verify" >&2
exit 1
done
It reads the local git directory and nothing else, so there is no dependency to be down. Note git rev-parse --git-dir rather than a hard-coded .git: it finds the state files inside a linked
worktree.
The message names git commit --no-verify on purpose: a guard with no way past it gets routed
around permanently rather than occasionally. --no-verify skips pre-commit but never
post-commit, which is where you print that the commit went in unchecked.
Git records what was staged, never who staged it
The other half you cannot solve inside git: is this staged path somebody else’s work? Git records what is in the index, never who put it there. The answer has to exist before the commit, written down by whatever handed the work out.
That is what Clawdline supplies: every dispatched task declares
the repository paths it may write, an empty list meaning read-only. The
pre-commit hook asks the local app what each live task claimed, then refuses a commit staging a
path owned by another line of work.
Ownership is the line of work, not the window: the dispatching session may commit the paths it claimed, and at landing it has to.
The two checks fail in opposite directions on purpose. The sequencer check refuses, because it depends on nothing. The ownership check asks a service that is not always running, so it warns, names what it cannot vouch for, and exits zero.
A check that depends on nothing may refuse. A check that depends on a service being up must not, because then the service being down becomes an outage of your ability to work.
When it refuses, unstage — and what the hook cannot see
The remedy is two commands; the obvious-looking third one is a trap:
git reset -- <their path> # unstages, without touching their bytes
git commit -m "<message>" # then commit your reviewed index
git commit -- <your paths> is the one to avoid, though it reads like the precise fix. It
commits the worktree content of those paths, not the hunks you staged, so it can absorb the other
session’s unstaged work.
The scope is narrow: pre-commit runs on git commit and nothing else.
| Operation | Does pre-commit run? |
Against our own three |
|---|---|---|
git commit |
Yes. The only one it is called for | Both August incidents here. Caught |
git commit --amend |
It runs, but whatever is already in HEAD passes through unchecked |
– |
git reset --hard |
No | The September one here. Not caught |
git checkout -- <path> |
No | – |
git stash |
No | – |
A conflict-free git merge |
No. It writes its commit without calling the hook at all | – |
The merge row is the one people assume goes the other way; verified on git 2.38.1. The guard stops
two of our three incidents, not the worst one. A reset --hard never reaches a pre-commit hook,
and reference-transaction fires only once the tree and the index are gone.
When to stop sharing the tree, and what that costs
git worktree add gives a session its own working tree and index, and the class of problem above
stops existing.
It is a trade, not an upgrade. A fresh checkout has no build cache, so its first minutes go on re-compiling what was on disk all along. Across 138 dispatched tasks on one machine, measured on 28 August 2026, isolated ones burned about 1.5 times the tokens per second. Medians: roughly 8,200 against 5,400 in the warm shared checkout. One machine, one person’s habits, and the direction is not subtle.
A long slice that touches many files belongs in its own worktree; short work stays in the shared checkout, behind a guard.
Neither option fixes the shared append-only file: two sessions touch different lines, git never
warns, and an uncommitted entry lands inside whoever next commits it. Ours did that on
3 September 2026. Read the hunks in git diff --cached -- <file>, because the stat says how many
lines and never whose.
Neither sees a person at that machine editing the same files. The half of the guard that depends on nothing still does, because it reads the git directory and not who you are.
The workflows guide covers what a task declares and who owns it afterwards; the stations page defines the vocabulary. Take one thing to your own repository tonight: make it the nine-line hook.