Agent isolation:"worktree" reuses a stale, per-session cached worktree path — survives manual teardown AND filesystem deletion

Status Open
Reported on v2.1.278
Maintainer reply None cached
Activity 4 comments · opened Sep 21, 2026

Summary

Agent(isolation: "worktree") dispatch fails immediately with Failed to resolve HEAD in <path>: git rev-parse failed during "Initializing…". The path it names is a worktree the session had already created and torn down manually (via a repo-provided git worktree wrapper) earlier in the same session. The harness appears to cache "this session's worktree slot for this repo" and reuse it on every subsequent isolation: "worktree" dispatch, without checking whether that path is still a real, registered git worktree.

Repro

  1. In a session, manually create and fully tear down a git worktree at some path P for repo R (i.e. git worktree add, do work, git worktree remove — outside of any Agent-tool dispatch).
  2. Dispatch Agent(isolation: "worktree") targeting repo R.
  3. It fails: Failed to resolve HEAD in <R>/P: git rev-parse failed.

What we ruled out

  • Not shared-checkout contention. Ran a 20-second, 50ms-interval poll of git rev-parse HEAD (and lock-file state) against the shared checkout, concurrently with a fresh isolation:"worktree" dispatch that reproduced the failure. The shared checkout resolved HEAD successfully on every single poll, zero failures, throughout.
  • Not stale directory content. git worktree list and the repo's own worktree registry both correctly showed path P as gone. We deleted the physical directory entirely (find P -delete, confirmed via ls) and retried the dispatch immediately — it reproduced the identical failure against the identical path, with nothing on disk there at all. This rules out leftover files/state as the cause.
  • Not a permission-hook rejection. The repo has an extensive PreToolUse hook pipeline (custom bash-guard/path-guard scripts). We checked its own telemetry log for the exact failure window: every recorded hook evaluation in that window shows outcome: allow — zero denials. Structurally, the "Initializing…" worktree-provisioning step appears to run before the spawned agent ever issues a tool call, so it never reaches repo-level PreToolUse hooks at all — consistent with the failure being internal to the harness's own spawn bootstrap, before any hookable action occurs.
  • First step of provisioning still runs. Immediately before the failure, we observed (via ps aux) the target directory being recreated on disk with the harness's own internal bookkeeping written into it (a conductor/presence-tracking subdirectory, keyed to our session ID) — so the harness clearly attempts something at that path before failing the HEAD check. It just never gets to actually creating a real linked worktree there.

What we think is happening

The harness maintains some form of cached association — "this session's designated worktree path for this repo" — that:

  1. Persists across separate isolation:"worktree" dispatches within the same session.
  2. Is not invalidated when that worktree is torn down through means other than the harness's own dispatch lifecycle (in our case, a repo-provided worktree-management CLI wrapping git worktree, used directly by the session, not via the Agent tool).
  3. Is not invalidated by the path no longer existing on disk, or no longer being a registered git worktree.

Environment

  • Claude Code 2.1.278 (macOS)
  • Agent tool, isolation: "worktree", subagent_type: general-purpose/fork.
  • Heavy concurrent usage on the host: 64 concurrent Claude Code processes running against the same repo at the time of reproduction — mentioning this since it may correlate with cache-invalidation pressure, though the bug reproduced cleanly in a controlled, low-noise retest too.

Related, but not exact matches

We found several open issues in this area that share partial symptoms but don't match this specific "cache survives filesystem deletion" mechanism:

  • #95644 — worktrees never auto-cleaned when a subagent finishes.
  • #83895 — a stale worktree path reused/reassigned across separate dispatches, causing a leaked process to run against the wrong session's directory.

This report's distinguishing evidence is the deletion test: the path was fully removed from disk and the failure still reproduced identically, which points at an in-memory/cached association rather than a filesystem or git-registry state issue.

Impact

Once triggered, this reliably blocks every subsequent isolation:"worktree" dispatch for the rest of the affected session (workaround: avoid isolation:"worktree" for the rest of that session; create the worktree manually and dispatch a non-isolated agent against the fixed path instead). A fresh session (new session ID) did not reproduce it in our testing, consistent with a session-ID-keyed cache.

View original on GitHub ↗

4 Comments

ianderrington · 23 days ago

Second data point: fresh session does NOT avoid this, and it's a different failure shape

Tested the "cache is session-ID-keyed" hypothesis from the report above with a genuinely fresh (non-resumed) session against the same repo. Result: does not confirm that hypothesis, and looks like a related-but-distinct failure mode.

What happened: 3/3 Agent(isolation:"worktree", subagent_type:"fork") dispatches failed on the first try, every time, with the same error shape: Failed to resolve HEAD in <repo>: git rev-parse failed.

Key difference from the original report: the path named in the error is the main checkout itself, not a stale worktree-slot subpath. It doesn't match any of the previously-used worktree names from the other session's history. So a stale, session-cached worktree-slot path is not the (sole) mechanism here — this fresh session had no such cache to inherit, and still failed identically.

Sanity check, same as before: git rev-parse HEAD against that exact main checkout succeeds every time when run directly, both before and after the failed dispatches. So whatever pre-flight HEAD check the harness runs internally is failing against a path where HEAD demonstrably resolves fine outside the harness.

Possible contributing factor, not root-caused: git worktree list on this repo currently returns 75 entries (45 of them ephemeral agent-*/wf_* names from past isolation:"worktree" dispatches across many sessions over time — apparently never garbage-collected, consistent with #95644's "worktrees never auto-cleaned" report). If the harness's own pre-flight step enumerates or validates existing worktrees as part of provisioning a new one, a repo with 75 accumulated entries is a very different cost/timing profile than a repo with a handful, and could plausibly explain a HEAD-check that's fine standalone but fails/times out inside the harness's own pre-flight.

Net: this repo may simply be in a bad state (worktree-list bloat from #95644 never firing) that makes the underlying isolation:"worktree" provisioning path fail regardless of session/cache state. Flagging as a lead for maintainers to check, not a confirmed root cause on our end — we don't have visibility into what the pre-flight check actually does.

ianderrington · 23 days ago

Third data point: isolation:"worktree" is NOT globally broken -- it's specific to this one repo

Ran a controlled comparison after fixing an unrelated internal gate that was blocking a prior retest:

  • This repo (families/supernal-coding), dispatched from the session whose own primary repo IS this one: still fails identically to every prior attempt -- Failed to resolve HEAD in <repo>: git rev-parse failed.
  • A different, small, lightly-used repo, dispatched from a session whose own primary repo IS that one: succeeded cleanly. All three diagnostic commands (pwd, git rev-parse --show-toplevel, git remote get-url origin) ran fine, no error at all.

Both sessions confirm isolation:"worktree" sandboxes the caller's own primary repo (matching #85448), not anything named in the dispatch prompt -- so this was a genuinely controlled comparison, not an artifact of targeting.

Working theory now: this isn't a global harness bug or a purely session-cache bug -- it's specific to (or triggered by) something about this one repo's state. The strongest candidate so far: this repo's git worktree list had accumulated 75 entries (45 of them ephemeral agent-*/wf_* names from past isolation:"worktree" dispatches, apparently never cleaned up per #95644). We've since manually swept ~20 stale ones down to ~55 and will report back if that changes anything, but haven't yet seen it flip to working.

If useful for debugging on your end: this repo is a large monorepo with many git submodules and a custom PreToolUse hook pipeline, and has been used heavily with isolation:"worktree" across many concurrent sessions over an extended period -- which is exactly the kind of accumulated state (stale worktree registrations, or whatever internal bookkeeping tracks "known worktrees for this repo") that could plausibly degrade only after sustained heavy use, rather than reproducing in a fresh/lightly-used repo.

ianderrington · 23 days ago

Root cause found: Agent-tool isolation:"worktree" mis-resolves its base path for a repo that is itself a git submodule

This explains everything reported in this thread, and narrows the bug to one specific code path.

The repo in question (families/supernal-coding) is itself a git submodule of a parent superproject (supernal-nova) -- its own .git is a gitlink FILE (gitdir: ../../.git/modules/families/supernal-coding), not a directory, and it in turn has ~11 submodules of its own.

Comparing where different worktree-creating mechanisms physically place their content, for the SAME repo:

| Creator | Naming | Placement | Correct? |
|---|---|---|---|
| A repo-provided worktree CLI wrapper (git worktree add under the hood) | wf_* / feature names, under .claude/worktrees/<name> or .worktrees/<name> | <repo>/.claude/worktrees/<name>/.git -- a normal sibling of the visible checkout | ✅ correct |
| Agent(isolation:"worktree") | agent-<hash>, under .claude/worktrees/<name> | <REPO'S OWN GITDIR>/.claude/worktrees/<name>/.git -- i.e. .git/modules/families/supernal-coding/.claude/worktrees/<name>/, physically INSIDE the gitdir tree, not beside the checkout | ❌ wrong |

Verified directly via git worktree list --porcelain and the gitdir file each worktree's admin entry points to. Every agent-* worktree for this repo has its actual working-tree content nested inside .git/modules/families/supernal-coding/... instead of alongside families/supernal-coding itself. Every wf_* worktree for the exact same repo is placed correctly.

A repo that is NOT a submodule of anything (tested: a small standalone repo, .git is a real directory) does not show this problem -- Agent(isolation:"worktree") places its worktree correctly there (<repo>/.claude/worktrees/agent-<hash>/, a normal sibling location), and the diagnostic dispatch succeeds cleanly, no rev-parse error.

Hypothesis for the actual bug: whatever resolves the base path for a new Agent-tool worktree likely calls something equivalent to git rev-parse --git-dir (or similar) and appends the worktree subpath to that result, rather than resolving the visible working-tree root (git rev-parse --show-toplevel, or the submodule's own working directory). For a normal top-level repo these two resolve to essentially the same place, so the bug is invisible. For a repo that is ITSELF a submodule, --git-dir resolves to the nested .git/modules/<path> location while the actual checkout stays at a completely different, non-nested path -- and a worktree created relative to the former is structurally broken from the start, which plausibly explains both the original Failed to resolve HEAD failures and the accumulated worktree-list bloat reported earlier in this thread (any operation on these malformed worktrees, including cleanup, likely also misbehaves).

This also explains why the bug reproduced reliably for families/supernal-coding specifically across every attempt in this thread, while a controlled test against a non-submodule repo (dispatched from a session whose own primary repo was that non-submodule repo) worked cleanly every time.

ianderrington · 23 days ago

Correction to my earlier root-cause claim: it's submodule + extensions.worktreeConfig, not submodule alone

My prior comment claimed "any repo that is itself a git submodule" triggers this. That was wrong, and I have a clean, reproducible test proving the narrower, precise condition.

Test setup: a fresh, genuinely vanilla submodule pair (outer repo + inner submodule, git submodule add, nothing else configured).

  1. Vanilla state (no extensions.worktreeConfig set): dispatched Agent(isolation:"worktree") from a session rooted in the submodule. Succeeded cleanly -- worktree placed correctly at <submodule>/.claude/worktrees/agent-<hash>, no error.
  2. Toggled extensions.worktreeConfig to true on that same submodule, nothing else changed. Re-ran the identical dispatch. Failed, with the harness's own error message naming the exact mechanism:

``
Refusing to use <path>/.claude/worktrees/agent-<hash> as an isolation worktree:
git resolves its working tree to <path>/../.git/modules/<name>
(a core.worktree redirect, or a checkout discovered above it),
so commands run there would write outside the worktree.
``

  1. Toggled extensions.worktreeConfig back off, re-ran again. Succeeded again.

Off -> on -> off tracked success -> fail -> success exactly, on the identical repo, with the identical dispatch. This isolates the real trigger to a single git config value: extensions.worktreeConfig=true on a repo that is itself a submodule -- not submodule-ness alone.

This also explains why families/supernal-coding (the repo in every prior report in this thread) reproduces reliably: it has extensions.worktreeConfig=true deliberately enabled (a real, legitimate git feature, turned on there to fix a separate, earlier core.bare/core.worktree corruption issue this repo hit from heavy concurrent worktree usage). It is not a misconfiguration on our end -- it's a documented, load-bearing setting for that repo's own concurrency needs, and it happens to trigger this harness bug as a side effect.

Likely mechanism, now narrower: when extensions.worktreeConfig is enabled, git stores certain config (including, plausibly, core.worktree) per-worktree rather than in the shared submodule config. If the Agent tool's own worktree-provisioning code reads/writes this value in the wrong scope, or doesn't account for the per-worktree config file at all, a newly created worktree could end up with a core.worktree value that still points at (or resolves back to) the submodule's own gitdir rather than the new worktree's own directory -- consistent with the error text above.

We've updated our own internal gate to the corrected, narrower condition (submodule AND worktreeConfig enabled, not submodule alone) so it no longer over-blocks a plain submodule that doesn't have this problem.