[Bug] Unidentified process silently runs `git pull --ff-only` and checks out main mid-session (Windows)

Resolved 💬 3 comments Opened Apr 9, 2026 by rucNick Closed May 22, 2026

Summary

During a multi-hour Spec-2 development session in a Windows / Git Bash environment, I encountered two "ghost checkout" events where an unidentified external process silently executed:

  1. git checkout main (swapping HEAD to main mid-session)
  2. git pull --ff-only origin main (fast-forwarding local main)

Both events happened while I was actively working on feature branches. Neither was triggered by any command I issued. The effects:

  • Ghost 1: My git commit landed on main instead of the feature branch I had just created, because HEAD had been swapped in the window between my git checkout -b feat/... and my git commit.
  • Ghost 2: gh pr create failed with head branch "main" is the same as base branch "main" because HEAD had been swapped from feat/... back to main between my git commit and my subsequent git push.

Both events were recoverable (via git branch -f + git reset --hard origin/main) but added ~10 minutes of diagnosis each.

Reflog evidence

Ghost 1 (Spec-2 Batch 6a hardening)

HEAD@{3}: checkout: main → fix/spec2-batch6a-hardening   # I ran: git checkout -b fix/spec2-batch6a-hardening
HEAD@{2}: checkout: fix/spec2-batch6a-hardening → main   # GHOST — I did NOT run this
HEAD@{1}: pull origin main: Fast-forward                  # GHOST — I did NOT run this
HEAD@{0}: commit: 41bbf52                                 # My commit landed on main (because HEAD had been swapped)

Ghost 2 (Spec-2 Batch 6d)

HEAD@{5}: checkout: fix/spec2-batch6c-recompute-latest-activity → main
HEAD@{4}: pull --ff-only origin main: Fast-forward         # GHOST — I never ran `git pull` with --ff-only
HEAD@{3}: checkout: main → main                            # GHOST — no-op self-checkout
HEAD@{2}: checkout: main → feat/spec2-batch6d-inline-top3-thread-entries
HEAD@{1}: commit: a8218e3                                   # My commit (correctly on feature branch)
HEAD@{0}: checkout: feat/spec2-batch6d-inline-top3-thread-entries → main   # GHOST — HEAD swapped back to main

Key evidence: the string pull --ff-only origin main: Fast-forward appears in reflog multiple times, but I never manually ran git pull --ff-only. All my manual pulls were bare git pull origin main (no --ff-only flag). The --ff-only variant is NOT from any command I issued through Claude Code's Bash tool.

Investigation — ruled out sources

I systematically investigated every candidate source for the ghost operations. The following are confirmed NOT the cause:

| Candidate | Evidence |
|-----------|----------|
| Claude Code project .claude/settings.local.json hooks | File only has permissions + enabledMcpjsonServers, no hooks |
| Claude Code user ~/.claude/settings.json hooks | File only has enabledPlugins + effortLevel + mcpServers, no hooks |
| .git/hooks/ | Only .sample files, no active hooks |
| superpowers plugin SessionStart hook | Read session-start script source — only outputs skill context to additionalContext, no git operations |
| security-guidance plugin hook | PreToolUse with matcher Edit\|Write\|MultiEdit — no git operations |
| Codex plugin SessionStart/SessionEnd/Stop hooks | Read all 3 hook scripts — manage Codex companion state, no git operations. Also: stopReviewGate: false in state.json, so Stop hook is gated off |
| IntelliJ IDEA | Not running — tasklist showed no java/idea process; .idea/workspace.xml git-widget-placeholder was stale from a branch several hours old, proving IDE never caught up |
| VS Code / Cursor / Fleet | Not in process list |
| Global shell profiles | ~/.bashrc, ~/.bash_profile, ~/.profile, ~/.bash_login all do NOT exist |
| Global ~/.gitconfig | Only user email/name + safe.directory; no aliases, no core.hooksPath, no auto-fetch |
| System / global git config | Clean Git for Windows defaults + LFS filter, zero aliases |
| gitnexus MCP server (running) | Source inspection of dist/server/git-clone.jscloneOrPull writes ONLY to ~/.gitnexus/repos/{name}/ cache, does not touch the active workspace |
| ai-memory MCP server (running) | Non-git-aware key-value memory store |
| Claude Code shell snapshot (~/.claude/shell-snapshots/snapshot-bash-*.sh) | 9 lines only: unalias, enable alias expansion, rg alias fallback, PATH export. Zero git operations |
| Running git.exe / codex.exe background processes | PowerShell Get-CimInstance scan returned no matches |
| Plugin cache grep for pull --ff-only or checkout main literals | Zero hits across all installed plugins |

Environment

  • OS: Windows 11 Pro (10.0.26200)
  • Shell: Git Bash (C:\Program Files\Git\bin\bash.exe) used by Claude Code's Bash tool wrapper
  • Claude Code version: 2.1.92
  • Node version: nvm4w managed, latest LTS
  • Workspace: Git submodule inside an umbrella repo
  • Repo: rucNick/MyKefi_RatemyDish_Resource (private)
  • Plugins enabled: superpowers, code-review, code-simplifier, claude-md-management, security-guidance, commit-commands, claude-code-setup, pr-review-toolkit, codex
  • MCP servers: gitnexus, ai-memory

Reproducibility

Both events happened during intensive git + mvnw test cycles (Batch 6a hardening + Batch 6d implementation). The ghost seems to fire at low frequency — 2 events in ~60 git commits during the session. I have not been able to pin down a precise trigger condition, but both events shared these characteristics:

  1. Happened after a ./mvnw test or git operation that touched many files under the working directory
  2. Happened within seconds of a git commit on a feature branch
  3. Both pull --ff-only origin main reflog entries landed without any user-issued git pull command in that window

Workaround mitigations I adopted

  1. Single-shell commit+push: combine git add && git commit && git push -u origin <branch> in ONE Claude Code Bash tool call to minimize the window where HEAD can be swapped between two tool invocations
  2. Always explicit branch name on push: git push -u origin <branch-name> (never bare git push)
  3. gh pr create --head <branch>: always explicit head
  4. Installed .git/hooks/pre-push in the affected repo that aborts if HEAD is on main but the push target is a non-main feature branch (signature of a ghost checkout)

These workarounds are effective but require discipline — they don't fix the root cause.

Asks

  1. Can Claude Code produce trace logs of shell commands it runs in its own internal machinery (not the Bash tool exposed to the model)? If Claude Code has any internal git bookkeeping, a trace would confirm or rule it out as the source.
  2. Does Claude Code invoke git internally at any point during session startup, context compaction, file edit commits, or background maintenance? The ghost events correlate loosely with intensive file system activity.
  3. Can the Bash tool snapshot mechanism source any file beyond ~/.claude/shell-snapshots/snapshot-bash-*.sh? The snapshot I inspected is benign, but perhaps there's a parallel init script I'm not aware of.
  4. Is there a GIT_TRACE or equivalent diagnostic mode I can enable for the next session to catch the ghost in the act?

Priority

Low severity, medium nuisance. Recoverable in all observed cases, but silent corruption of feature-branch commits is a significant workflow risk. A single unnoticed ghost on an important commit could land it on main directly, bypassing review.

I'm happy to run additional diagnostics or enable trace logging for a follow-up session if that would help triage.

---

Filed by Claude Code session user rucNick on 2026-04-09 after concluding a ~6 hour Spec-2 feature development session where the bug manifested twice.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗