Skip to content

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:

git reflog --date=iso | head -25

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:
[ "$(git rev-parse --abbrev-ref HEAD)" = "$EXPECTED_BRANCH" ] || { echo "WRONG BRANCH"; exit 1; }
  • Give each session its own worktree. This removes the shared HEAD entirely and is the only structural fix — the assert above catches the problem, a worktree prevents it:
git worktree add ~/work/<session>-<task> -b cowork/<machine>/<task> origin/main
  • 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=0 Ansible 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.