Concurrent Sessions in One Git Checkout: Commits Land on the Wrong Branch¶
Symptom¶
A commit is made and confirmed. The merge that follows reports success and pushes. But the
merged result does not contain the change — and the working tree has quietly reverted
to a version without it. No error at any step. Every command exits 0.
The tell is a merge whose diffstat is smaller than what you committed:
→ Pushing main...
430b6c0..557407a main -> main
→ Deleting local branch...
Deleted branch cowork/majorm5pro/my-feature (was 603a062) # <-- not the SHA you committed
That (was ...) SHA is the branch's real tip. If it isn't the commit you just made, your
commit went somewhere else.
Cause¶
Two sessions sharing one working copy. git checkout is a property of the checkout,
not of a session. A concurrent session switching branches moves HEAD for everyone using
that directory. Any git add / git commit that follows applies to whatever branch the
other session left checked out.
Real sequence (2026-09-07, ~/MajorAnsible, reconstructed from git reflog):
10:39 session A: checkout -B cowork/majorm5pro/desec-ddns ... # A's branch
10:43 session A: commit 603a062 # lands on A's branch ✅
11:03 session B: checkout code/majorm5pro/ai-crawler-policy # HEAD moves — A is not told
11:07 session B: commit 85fd342 # B's own work
11:07 session A: commit dd13c69 # lands on B's BRANCH ❌
11:07 session A: major-merge <A's branch> # merges 603a062 — one behind
Session A's second commit was never on session A's branch, so the merge was correct about what it was told to merge and still shipped the wrong thing.
The merge tool cannot detect this
major-merge verified a clean tree, fetched, previewed commits, and merged exactly the
branch it was given. Every check passed. The branch simply did not contain the commit,
and nothing in git's model makes that suspicious — a branch being one commit older than
you expect is indistinguishable from a branch you never committed to.
Why the branching convention does not cover this¶
The MajorVault branching convention (CLAUDE.md) exists because 3 Cowork + 3 Code sessions
run concurrently. It guards two failure modes:
| Guarded | Not guarded |
|---|---|
Branching off a stale main |
A concurrent checkout moving HEAD mid-task |
Committing directly to main |
Your commit landing on another session's branch |
Both guarded cases assume each session controls its own HEAD. With a shared checkout that
assumption is false, and the convention's own advice — "create the branch before editing"
— does not help, because the branch is created correctly and then silently switched away
from underneath you.
This corrects the claim in Concurrent Claude Sessions Silently Clobber Vault Journal Writes that git-backed repos are "protected by the branching convention." They are protected from stale-main and direct-to-main collisions. They are not protected from this.
Diagnosis¶
git reflog is authoritative and shows every HEAD move with timestamps, including the
other session's:
Look for a checkout: moving from <your branch> to <someone else's branch> that you did not
issue, followed by your own commit: line. Then confirm where the commit actually went:
git branch -v # is your commit some other branch's tip?
git merge-base --is-ancestor <your-sha> main \
&& echo "in main" || echo "NOT in main"
git log --oneline main..<other-branch> # your commit sitting on their branch
Recovery¶
The commit is never lost — it is a normal commit, just parented elsewhere.
git fetch origin
git checkout -B recover/<task> origin/main
git cherry-pick <your-sha>
# VERIFY before merging — this is the step whose absence caused the incident
<check the actual file contents are present>
git checkout main && git merge --no-ff recover/<task>
<verify contents again> # verify BEFORE pushing
git push origin main
If the other session has already merged their branch, your stray commit rides along with
theirs. That resolves cleanly only if the content is byte-identical to what you
re-merged. If you edited the same file again in between, you get a conflict — and on an
ansible-vault-encrypted file, git cannot merge it at all.
Prevention¶
- Assert the branch immediately before every commit. One line, and it turns a silent mis-commit into a loud stop:
- Give each session its own worktree. This removes the shared
HEADentirely and is the only structural fix — the assert above catches the problem, a worktree prevents it:
-
Verify merge contents, not the merge's exit code. After any merge, confirm the files you expected are actually present before pushing. This is the git-side twin of the
changed=0Ansible staleness trap: a green result that means "shipped the old thing." -
Treat an unexpected
(was <sha>)on branch deletion as an alarm, not noise. It is the one place git tells you, in passing, which commit the branch really pointed at.
Related¶
- Concurrent Claude Sessions Silently Clobber Vault Journal Writes — the same root cause (no locking between sessions) against vault files instead of git
- Ansible Check Mode False Positives — the same "green result, wrong outcome" failure class