Worktree virtual-overlay evaporation: Edit/Write operations silently target ghost files when worktree disappears mid-run

Resolved 💬 4 comments Opened May 23, 2026 by DAddYE Closed Jun 23, 2026

Severity: P0 (silent data loss; agents ship "green" tests against ghost files)

Summary

When a Claude Code subagent is dispatched with isolation: "worktree",
the harness occasionally reports the worktree as created while the
physical worktree directory does not exist on disk. The subagent's
Edit, Write, and Read tool calls then succeed against a transient
sandbox overlay; cargo test / bun test / etc. run inside Bash see
real files via the overlay AND pass; but at session/subagent exit the
overlay evaporates and the work disappears.

Two failure modes observed:

  1. Never created. The worktree directory is absent at the very

first subagent Bash. Every subsequent Edit/Write/Read hits the
overlay. Tests pass green against nothing. No git commits succeed
(because no worktree branch ever existed). All work is lost when
the overlay evaporates.

  1. Mid-run evaporation. STEP-0 dispatch verification passes (the

worktree's .git is a real gitlink); the coder commits 4 commits
to git successfully; the worktree directory then vanishes mid-run
(git worktree list no longer reports it; ls .claude/worktrees/agent-<id>/
returns ENOENT). The commits survive because git's object store
is separate from the worktree filesystem, BUT subsequent Edits go
to the overlay and evaporate.

Reproduction shape

Dispatch a subagent with isolation: "worktree". Hit rate ~89% across
~12 dispatches in a single ~6-hour session, 2026-05-22. The harness
log surface gives no warning; the agent receives no failure signal;
Read of a worktree path returns the overlay content (which mirrors
the in-memory state) so a self-check using only Claude Code tools
cannot detect the condition.

A user-side detection workaround: from a Bash tool call, ls -la
.claude/worktrees/agent-<id>/.git
and require the output be a regular
file containing gitdir: .... This is reliable because Bash ls hits
the real filesystem, not the overlay. We are landing this as a
PreToolUse Edit/Write hook (worktree-existence-guard.ts) but the
underlying platform bug should be fixed upstream.

Witness commits / artifacts from one session

Single-session evidence trail (2026-05-22, tau project):

| Commit | Lane | Failure mode |
|---|---|---|
| a6f9d9b4c3bc9e7c1 (agent id) | cli.syntax_highlight | Never created. 3744/0 tests passed against ghost files; merge structurally succeeded; worktree branch did not exist on disk |
| adc581b0bbe13428b (agent id) | M1a state-snapshot cleanup | Mid-run evaporation after 4 commits |
| Lane M1b | tpol-manual-worktree-pivot adopted | Pivot away from harness-managed path |
| Lane M1c, M3, M4, M5a, M5b (5 further dispatches) | Various | Required tpol-manual-worktree-pivot to /private/tmp/<lane>-<id>-wt/ for survivable work |

docs/bugs/worktree-coder-virtual-overlay-evaporation.md in the
tau repo (private; can share on
request) documents both occurrences with full timing.

Workaround in use

Coders pivot to a manually-created worktree at /private/tmp/<lane>-<id>-wt,
created BEFORE the first Edit/Write via:

git worktree add /private/tmp/<lane>-<id>-wt -b worktree-agent-<id>
ls /private/tmp/<lane>-<id>-wt/.git # must be a regular file

This sidesteps the harness-managed .claude/worktrees/agent-<id>/
path entirely. The pivot is documented in our agent-memory as
tpol-manual-worktree-pivot.

What we would like

  1. Pre-dispatch verification: the harness should refuse to spawn

a worktree-isolated subagent if git worktree add fails. A clear
error to the coordinator is much better than a silent overlay.

  1. No transparent overlay over absent worktree dirs: if the

physical directory does not exist, Edit/Write should fail loudly
rather than succeed against an in-memory layer.

  1. At minimum, surfacing: a notification event when an overlay-

only write happens, so PreToolUse hooks can catch the condition
without needing the workaround above.

Detection mechanism we shipped on our side

We added a PreToolUse hook on Edit / Write / MultiEdit at
.claude/hooks/worktree-existence-guard.ts that resolves the target
path against both worktree shapes (.claude/worktrees/agent-<id>/...
and /private/tmp/<lane>-<id>-wt/...), lstat()s the worktree's
.git, and denies the tool call unless .git is a regular file with
a gitdir: pointer. The hook fires from a real Node process spawned
by the harness, so it hits the real filesystem (not the overlay) and
catches both failure modes. It has no bypass envvar; the underlying
platform issue is "the harness lies about this" and a bypass would
just let the next typo through. Code:
https://github.com/DAddYE/tau/blob/main/.claude/hooks/worktree-existence-guard.ts
(after we push the current branch; happy to mirror the source here on
request).

Thanks; happy to provide more reproduction data, harness logs, or run
diagnostics on request.

cc Captain (Jonathan Archer / @ddagostino).

View original on GitHub ↗

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