claude --worktree fixture pollutes parent worktree's HEAD and resets git identity to Test <test@test.com>

Open 💬 1 comment Opened Jun 15, 2026 by Raleigh17-1973

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

The claude --worktree test fixture (which creates temporary worktrees under %TEMP%\worktree-test-* with branches named change/task-alpha, change/task-beta, change/my-task-01, change/reuse-task, change/cleanup-task) performs two unsafe operations against the parent worktree's checkout:

  1. Resets the parent worktree's git config user.name and user.email to Test <test@test.com> — and this reset persists after the fixture exits, so subsequent legitimate commits inherit the bad identity unless caught by a pre-commit hook.
  2. Makes empty commits authored as Test <test@test.com> directly on top of the parent worktree's current branch HEAD.

Both happen without prompt or explicit user action — solely as a side effect of the fixture running. This violates the isolation guarantee the --worktree flag implies: a worktree's purpose is to give a session its own HEAD and working directory without touching the parent.

The most damaging consequence in our case: we believe uncommitted in-flight work in the parent worktree was destroyed during the fixture's run. The lost work does not appear in git stash list, git fsck --lost-found, or git reflog — so the destruction is opaque and unrecoverable.

What Should Happen?

  • The test fixture should never write to the parent worktree's HEAD or branch.
  • The test fixture should never modify the parent worktree's git config (user.name, user.email, or anything else).
  • All fixture-created worktrees and branches should be cleaned up before exit, regardless of success or failure.
  • If the fixture must use a Test identity, that should be scoped via GIT_AUTHOR_NAME / GIT_AUTHOR_EMAIL env vars per-command, never written to persistent git config.
  • Uncommitted working-tree state in the parent worktree should be untouched, period.

Error Messages/Logs

After the fixture ran, the parent worktree's branch HEAD looked like this:

$ git log -10 --format='%h %an <%ae> %s' future-proofing
<sha-8> Test <test@test.com> init
<sha-7> Test <test@test.com> init
<sha-6> Test <test@test.com> init
<sha-5> Test <test@test.com> init
<sha-4> Test <test@test.com> init
<sha-3> Test <test@test.com> init
<sha-2> Test <test@test.com> init
<sha-1> Test <test@test.com> init
ac4a162e5 Jeff Hoyt <real-email> chore: CI retrigger
7719f19d0 Jeff Hoyt <real-email> fix(okr): chip commit for templates/CSV 500s

$ git config user.email
test@test.com   # was jeffrey.r.hoyt@gmail.com before the fixture ran

$ git worktree list
[primary worktree paths...]
%TEMP%\worktree-test-<id1>   change/task-alpha
%TEMP%\worktree-test-<id2>   change/task-beta
%TEMP%\worktree-test-<id3>   change/my-task-01
%TEMP%\worktree-test-<id4>   change/reuse-task
%TEMP%\worktree-test-<id5>   change/cleanup-task

# branches still in repo after fixture exit:
change/task-alpha
change/task-beta
change/my-task-01
change/reuse-task
change/cleanup-task

$ git stash list
(empty)

$ git fsck --lost-found 2>&1 | grep dangling
(only old/unrelated dangling commits — no recoverable working-tree state)

$ git reflog --date=iso | grep -E 'reset|checkout' | head -10
(no reset --hard or checkout entries pointing at the lost state)

Steps to Reproduce

  1. Clone any non-trivial repo (we used a monorepo with npm workspaces, ~381 commits on a long-lived feature branch).
  2. Check out a feature branch and make some uncommitted working-tree changes (representing in-flight work).
  3. From the parent worktree, run claude --worktree <some-topic> and trigger whatever internal path causes the test fixture to execute (we did not invoke the fixture explicitly — it ran as part of an internal Claude Code session step; we are unsure of the exact trigger).
  4. Wait for the fixture to complete and Claude Code to exit.
  5. From the parent worktree, observe:
  • git log -10 --format='%h %an <%ae> %s' → 8 empty commits authored Test <test@test.com> stacked on top of the legitimate HEAD.
  • git config user.email → reset to test@test.com.
  • git worktree list → 4–5 %TEMP%\worktree-test-* paths still present.
  • git branch | grep change/ → 5 leftover change/task-* branches.
  • Working-tree files that existed before the fixture ran are absent, with no trace in git stash list, git reflog, or git fsck --lost-found.

Note: We cannot pinpoint exactly which Claude Code action triggered the fixture, which is itself a problem — the destruction was opaque. If the team can share the conditions under which worktree-test-* temp worktrees get created, that would help users build defensive practice.

Claude Model

None

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.140 (Claude Code)

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

PowerShell

Additional Information

Impact

Our parent worktree was on a long-lived feature branch (~381 commits, monorepo with npm workspaces). The fixture added 8 empty Test-authored commits to that branch's HEAD. The identity reset persisted after the fixture exited, so subsequent legitimate commits would have been authored Test <test@test.com> if not caught.

We have a husky pre-commit hook that validates Co-Authored-By trailers against a canonical model-name list (.husky/check-claude-coauthor-format). That hook is the only reason the bad identity didn't enter our committed audit-chain — without it, the contamination would have been silent and lasted across many commits.

Forensic notes

Recovery investigation after the incident:

  • No stashes created during the fixture's run.
  • No dangling commits in git fsck --lost-found (only old/unrelated dangling commits from prior weeks).
  • No reflog entries pointing at the lost work.
  • The 8 empty commits were stacked directly on the legitimate branch HEAD; removing them required git reset --hard <pre-fixture-SHA>.
  • The temporary worktrees under %TEMP%\worktree-test-* had to be torn down manually via git worktree remove --force <path>.
  • The 5 change/task-* branches had to be deleted manually via git branch -D <name>.

Why this is serious

The opacity of the destruction is the safety issue. With full forensic tooling (reflog, fsck, stash, all-branch log scan, sibling-clone scan), we could not recover. A user who didn't investigate would have lost work silently and might not have noticed the identity reset until much later — possibly after pushing commits under the wrong author.

Workaround for users while this is open

Users running Claude Code on long-lived feature branches with uncommitted work should:

  1. Commit or stash everything before invoking claude --worktree.
  2. Audit git config user.email after every Claude Code session.
  3. Audit git log -5 for Test-authored commits before pushing.
  4. Run git worktree list and look for stray %TEMP%\worktree-test-* entries.
  5. Install a canonical-author pre-commit hook to catch the identity-leak case during commit.

Happy to provide more detail or session telemetry — please let us know what would help triage.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗