blog

Two Claude Code sessions, one checkout: stopping them committing each other's work

Several agent sessions in one repository share one git index. The pre-commit check that refuses, the half that can only warn, and how Clawdline knows which line of work a path belongs to.

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.

session A: git add session B: git add one shared index git commit

Two sessions stage into the same index; a commit with no pathspec takes the whole of it.

There is one index and it has no owner. It records what was staged, never who staged it — so the commit with no pathspec takes both, and afterwards the diff cannot tell you which hunk belonged to whom. Stopping that needs somebody to have written the attribution down before the commit.

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.

Common questions

Can two Claude Code sessions work in the same repository at once?
Yes, and the thing to know before you do is that they share one git index. A git add from one session and a git add from another produce one staged set, and a plain git commit with no pathspec commits all of it — git has no per-session view of the staging area and never has. Two windows in one checkout is enough to reach that; it is not an exotic setup. The workable versions are a pre-commit hook that refuses a commit it can prove belongs to another line of work, or one git worktree per session so there is no shared index to fight over. Choosing neither is choosing to find out.
How do I stop one AI coding agent committing another agent's changes?
Put the rule somewhere that can refuse, because a rule written in a prompt is read past exactly when the session is busiest. The half you can build alone is nine lines of shell: a pre-commit hook that exits non-zero when MERGE_HEAD, CHERRY_PICK_HEAD, REVERT_HEAD or a rebase state directory exists in the git directory, because then your commit is concluding an operation you may not have started. The half you cannot build alone is telling whose staged path is whose — git records what was staged and never who staged it, so that answer has to be written down before the commit by whatever handed the work out.
Do I need a separate git worktree for each agent session?
Not always, and it is a trade rather than an upgrade. A worktree per session gives each one its own index and working tree, which removes this whole class of problem — but a fresh checkout has no build cache and none of the dependencies the shared tree already holds, so it spends its first minutes rebuilding what was on disk the whole time. Measured across 138 dispatched tasks on our own machine on 28 August 2026, isolated ones burned about 1.5 times the tokens per second of ones working in the warm shared checkout. Rule of thumb: a long slice that touches many files goes in its own worktree, short work stays in the shared tree behind a guard.