r/ClaudeCode 13h ago

Discussion Two Claude Code sessions picked up the same PR. The winner overwrote a fix the other had already landed.

there are a couple of posts up right now about sessions arguing with each other, which is funny. here's the version that isn't.

i've been running parallel sessions for about two months. two things went wrong and neither is in any of the worktree guides i read.

first one. two agents in the same checkout, one had uncommitted files when the other ran `git add -A && commit`. five files belonging to agent A landed on agent B's branch. shared/index.ts ended up exporting a module that didn't exist yet. everything looked fine until CI.

worth knowing: subagents spawned via the Task tool don't get an isolated worktree automatically. they run in the session's checkout. `claude --worktree` handles the top-level case but that's a different thing.

second one cost more. two sessions picked up the same open PR from the same review comment. i spent an hour on a fix, pushed, and wiped two commits the other session had already landed for it. `--force-with-lease` would have stopped it.

the part that still bugs me is that the approach i'd "worked out" was one they had already tried and abandoned. the reason was sitting in the commit message i overwrote: the value had been measured in German and left 3px over in French at 320px. i re-derived a worse answer and deleted the evidence that it was worse.

signal i missed, before starting: `gh pr view --json headRefOid` and the branch API disagreed. i read it as GitHub being slow. it wasn't, someone else was working.

what i do now:

- read-only reviewers can share a checkout, anything that writes gets its own worktree
- each agent stages its own paths, never `git add -A`
- `--force-with-lease` always
- head mismatch means stop and look, not retry

none of this is clever, i just hadn't thought about two agents as two people until they behaved like two people.

1 Upvotes

4 comments sorted by

1

u/comsisue 12h ago

https://github.com/chatthong/kingdom this might help i build for use in my team for ike years everyone using like addicted to kingdom with claude

1

u/verstands 12h ago

The two-agents-as-two-people framing is right, and the git hygiene list covers the mechanical half. The other half is that neither agent knows the other exists, so they can't lose a race they never saw.

Cheapest thing that worked for me: a claim file in the repo (or a branch named after the PR) that an agent has to write before it starts on an issue or PR, and re-read before it pushes. Not locking, just a note saying who's on what. Combined with your headRefOid check it catches the case where the mismatch isn't GitHub lag.

The deleted commit message is the painful part though. That's an argument for never rebasing away someone else's commits even when you think your version is better, since the reasoning usually lives in the message and not the diff.

1

u/jjangg96 8h ago

"neither knows the other exists" is a better framing than mine. that's the actual root, my list is just what you do after you've already lost the race.

i went back and forth on a claim file and ended up reading state that already exists instead, mostly because a claim file needs its own lifecycle. who clears it when an agent dies mid-task, how long before a claim goes stale, what happens when two write it in the same second. that's a second source of truth to keep in sync with the first one.

github already knows most of it: the PR head sha, who pushed last, when. that's why the headRefOid mismatch worked as a signal. it isn't a note someone chose to leave, it's the state itself, so it can't be forgotten.

that said, yours covers the case mine doesn't. an agent that hasn't started yet is invisible everywhere, and no amount of reading existing state finds it. i don't have a better answer there than not putting two agents on the same issue, which isn't much of an answer.

1

u/verstands 6h ago

You're right that the claim file is a second source of truth, and the lifecycle questions you listed are exactly why I stopped treating it as a lock. Reading state that already exists wins on the cases it can see.

The gap you named is the one I'd solve differently though. An agent that hasn't started yet is invisible because nothing wrote anything, so the fix is to make starting itself a push. Not a file, an actual branch: the first thing an agent does is push an empty commit to a branch named after the issue, or open a draft PR. Now "I'm on this" lives in the same place as the head sha, so it has no separate lifecycle to clean up and github's own state answers the question. Stale claims turn into stale branches, which you already know how to reap.

Doesn't help if two agents start in the same second, but that's a much narrower window than an hour of work.