[Bug] EnterWorktree/ExitWorktree tools don't restore core.bare config on exit

Status Closed — not planned
Reported on v2.1.139
Maintainer reply None cached
Activity 8 comments · opened May 12, 2026 · closed Jun 16, 2026

I have seen this issue for months, but today Claude Code was able to determine why. This has contributed to my hatred of work trees, but I see how they are useful, however Claude Code usage of them must be reliable.

Bug Description
Found the culprit. .git/config mtime is May 12 08:47:14 — minutes ago, right after my ExitWorktree --remove. The Claude Code harness EnterWorktree/ExitWorktree tools flip core.bare = true when you
enter a worktree, and don't restore it on exit. That's not documented in the tool descriptions, but the config mtime nails it.

History: this repo was originally a normal checkout (loose CLAUDE.md from Apr 14, package.json from May 11 09:21 — both still on disk). Every time EnterWorktree ran, the parent flipped to bare.
ExitWorktree(remove) doesn't reset it, presumably because other worktrees (avonos-final, platform-client-dataverse-extraction) exist and worktrees work fine against a bare parent.

The existing files at the root are leftover from before any of those EnterWorktree calls ever happened. They're not tracked while bare = true.

One-line fix (gives you back a normal working tree at this directory; doesn't affect the other worktrees — they work fine either way):

git config --local core.bare false

Want me to run that? After it, git status will work here and the loose files will be visible relative to main.

Environment Info

  • Platform: darwin
  • Terminal: Apple_Terminal
  • Version: 2.1.139
  • Feedback ID: 4ff26c9c-b9c6-4182-8bb9-c6dd03947a0b

Errors

[{"error":"Error: NON-FATAL: Lock acquisition failed for /Users/jeffdutton/.local/share/claude/versions/2.1.139 (expected in multi-process scenarios)\n    at z16 (/$bunfs/root/src/entrypoints/cli.js:2662:2257)\n    at tRH (/$bunfs/root/src/entrypoints/cli.js:2662:1337)\n    at processTicksAndRejections (native:7:39)","timestamp":"2026-05-11T23:36:58.241Z"},{"error":"Error: Plugin MCP server error - mcp-config-invalid: MCP server terraform invalid: Missing environment variables: TFE_TOKEN\n    at m7H (/$bunfs/root/src/entrypoints/cli.js:2663:36777)\n    at processTicksAndRejections (native:7:39)","timestamp":"2026-05-11T23:36:58.277Z"},{"error":"Error: Plugin MCP server error - mcp-config-invalid: MCP server terraform invalid: Missing environment variables: TFE_TOKEN\n    at m7H (/$bunfs/root/src/entrypoints/cli.js:2663:36777)\n    at async L (/$bunfs/root/src/entrypoints/cli.js:6416:11729)\n    at processTicksAndRejections (native:7:39)","timestamp":"2026-05-11T23:36:58.876Z"},{"error":"Error: Plugin MCP server error - mcp-config-invalid: MCP server terraform invalid: Missing environment variables: TFE_TOKEN\n    at m7H (/$bunfs/root/src/entrypoints/cli.js:2663:36777)\n    at async N (/$bunfs/root/src/entrypoints/cli.js:6416:12615)\n    at processTicksAndRejections (native:7:39)","timestamp":"2026-05-11T23:36:58.877Z"},{"error":"Error: Request was aborted.\n    at makeRequest (/$bunfs/root/src/entrypoints/cli.js:51:6192)\n    at processTicksAndRejections (native:7:39)","timestamp":"2026-05-11T23:42:07.685Z"},{"error":"AxiosError: timeout of 5000ms exceeded\n    at <anonymous> (/$bunfs/root/src/entrypoints/cli.js:121:13313)\n    at emit (node:events:92:22)\n    at <anonymous> (/$bunfs/root/src/entrypoints/cli.js:120:3321)\n    at request (/$bunfs/root/src/entrypoints/cli.js:123:2467)\n    at processTicksAndRejections (native:7:39)","timestamp":"2026-05-12T12:36:52.398Z"},{"error":"Error: Request was aborted.\n    at makeRequest (/$bunfs/root/src/entrypoints/cli.js:51:6192)\n    at processTicksAndRejections (native:7:39)","timestamp":"2026-05-12T12:44:08.517Z"},{"error":"Error: Request was aborted.\n    at makeRequest (/$bunfs/root/src/entrypoints/cli.js:51:6192)\n    at processTicksAndRejections (native:7:39)","timestamp":"2026-05-12T12:46:59.541Z"}]

View original on GitHub ↗

8 Comments

github-actions[bot] · 5 months ago

Found 2 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/51930
  2. https://github.com/anthropics/claude-code/issues/45201

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

j7an · 5 months ago

Independent repro + workaround

Hit this independently on a different repo (macOS 24.6.0). Different starting state from @jdutton's — mine was a normal non-bare checkout — and the corruption came in through Superpowers' using-git-worktrees skill (its Step 1a steers Claude Code agents toward EnterWorktree over git worktree add, which widens the blast radius beyond users who call EnterWorktree directly).

Forensics that line up with @jdutton's diagnosis

The mtime correlation is tighter than I expected:

  • .git/config rewritten with core.bare = true at one timestamp
  • .git/worktrees/<branch>/HEAD and index updated one second later

Sequence: shared-config write, then linked-worktree state update. Not in shell history. Not in Codex session JSONLs either (Codex doesn't have EnterWorktree, so it can't be the writer). Consistent with an EnterWorktree invocation.

How this case differs from #45201

Both this issue and @therickfactr's hit a "wrong config scope" defect, but in different forms:

| | #45201 | This issue (#58345) |
| --- | --- | --- |
| Starting layout | Intentional bare-hub (git clone --bare) | Normal non-bare checkout |
| What Claude Code writes | extensions.worktreeConfig = true (re-scopes the existing core.bare = true) | core.bare = true directly to shared config |
| What breaks | Existing worktrees inherit bare = true because no config.worktree overrides exist | Parent dir loses its working tree |

I checked: extensions.worktreeConfig is not set, and no config.worktree files exist under .git/worktrees/*/. So this is the simpler, non-extensions variant. Cross-linking #45201 and #27474 (core.hooksPath to $GIT_COMMON_DIR/config) — they look like three manifestations of the same root cause: the harness writes to shared/common git config when it should write to worktree-local config.

Durable workaround (macOS)

@jdutton's one-line fix (git config --local core.bare false) recurs every time EnterWorktree runs. To make the fix stick until the upstream bug is resolved:

git config --unset core.bare
chflags uchg .git/config

chflags uchg sets the BSD user-immutable flag — any process attempting to rewrite .git/config (including EnterWorktree) gets EPERM. That converts the silent corruption into a loud, diagnosable error, and the trap defends against the next call without further intervention.

To legitimately edit config later (adding a remote, etc.):

chflags nouchg .git/config && git config <k> <v> && chflags uchg .git/config

Linux equivalent is chattr +i .git/config (requires sudo). Windows has no clean equivalent without NTFS ACLs — affected Windows users may need to wait for the upstream fix.

Suggested unified fix direction

Across #58345, #45201, and #27474, the right invariant seems to be: EnterWorktree should write only to worktree-local config (git config --worktree <k> <v>) and never to $GIT_COMMON_DIR/config. That requires extensions.worktreeConfig = true first (and per-worktree config.worktree files seeded for existing worktrees, per #45201's fix proposal). If that invariant is hard to retrofit, a narrower fix specific to this issue would be: on ExitWorktree --remove, if no other linked worktrees exist and the shared config currently has core.bare = true from an EnterWorktree write, restore core.bare = false. The chflags trap will catch any regression of either invariant.

TheMostlyGreat · 5 months ago

Adding evidence from a different team — same root cause, broader symptom surface.

Three independent reproductions

In ArcadeAI/safeword with 4 active worktrees in .claude/worktrees/:

| Date | Trigger | Failed git op | Recovery |
|------|---------|---------------|----------|
| 2026-05-03 10:37Z | spawned follow-up chip; ref-bump on claude/ticket-137-138-unified-override correlated 1:1 with bare-flip event | git commit | git config core.bare false immediately restored |
| 2026-05-15 05:44Z | two sibling worktrees actively writing for ~6-11 min | git status -sb in parent worktree | same one-liner restored |
| 2026-05-17 (today) | mid-session, during back-to-back work across multiple worktrees | git status, git mv, git push -u — hit ~5 times in one session | manual reset each time |

Symptom surface is broader than EnterWorktree/ExitWorktree

The original report focuses on Enter/Exit not restoring core.bare. The 2026-05-17 reproduction shows the race firing mid-session, on ad-hoc reads (git status, git mv, git push) — well after any Enter/Exit boundary. Whatever holds core.bare = true transiently during worktree-creation can be observed by any git call that races with another worktree's creation/modification, not just the Enter/Exit-adjacent ones.

The error message is misleading

fatal: this operation must be run in a work tree blames the user's CWD. The actual cause is a shared-config race in .git/config on the parent. Users naturally chase CWD/path issues for hours before finding the real cause.

Workarounds in the wild are partial

We shipped a defensive core.bare = false reset inside .husky/pre-commit (commit aeb342b). It catches the race only inside git commit — and on the 2026-05-17 session, the reset fired correctly during commits while every git status/git mv/git push between them still failed unprotected. Now extending to a PreToolUse hook on Bash(git *) to cover ad-hoc reads.

This is a worse fix-cost asymmetry than it looks: every project using Claude Code with worktrees needs its own bespoke mitigation, and even with one, partial coverage is the norm.

Proposed upstream fix (in order of preference)

  1. Eliminate the toggle — git worktree add --no-checkout + post-fixup avoids touching core.bare on the parent
  2. flock on .git/config during the toggle so concurrent reads block instead of seeing the bare-true intermediate state
  3. Document the race in the Enter/Exit/Agent-isolation tool descriptions so users know to add defensive resets

Related: #51930 (closed) showed the same root cause via the Agent-tool isolation path. So this race affects multiple Claude Code worktree-touching surfaces.

Dangy · 5 months ago

Adding another data point — same bug class, different key.

On Windows 11 / PowerShell 7.4 / Claude Code VSCode extension, every Agent({ isolation: "worktree", ... }) dispatch rewrites the parent repo's core.hooksPath from a versioned in-repo path (e.g. tools/git-hooks/) to the absolute legacy .git/hooks directory. Reproduced 4/4 times across four independent sub-agent dispatches — sub-agent briefs explicitly forbade git config calls, so the rewrite is happening in the harness's worktree-add or teardown step, not in the sub-agent's actions.

Impact in our setup: versioned commit-msg / post-commit / pre-push hooks all go silently inert in the parent repo. Trailer injection stops, derived-state refresh stops, branch-protection pre-push guards stop firing. No error is emitted — git commit proceeds normally. Only an explicit git config --get core.hooksPath check reveals the drift, which is why we added a session-start sentinel.

Workaround: re-run our idempotent hook installer (git config core.hooksPath <path>) after every isolated sub-agent dispatch.

Same root cause as the core.bare case here and the origin URL case in #51106 — the worktree harness writes to the parent's shared .git/config and doesn't restore on exit. Suggests the proper fix is general (snapshot+restore the parent config, or operate on the worktree's own config via extensions.worktreeConfig) rather than per-key.

github-actions[bot] · 4 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

j7an · 4 months ago

@bcherny please consider prioritizing bug fixes over new features so that effort isn't wasted on re-opening un-fixed bugs auto-closed by Github Actions.

marcindulak · 4 months ago
github-actions[bot] · 1 month ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.