Worktree-relocated background agent sessions crash-loop on reopen from the agents view ("No conversation found with session ID")
Background agent sessions relocated into a git worktree crash-loop on reopen from the agents view ("No conversation found with session ID")
Summary
Opening certain entries from the agents view crashes the worker repeatedly with:
No conversation found with session ID: <uuid>
[worker crashed (exit 1) — respawning…]
The worker respawns ~4 times, then gives up and drops back to the agents view. The broken entry stays in the list — it is not dismissed by the crash and only disappears after Ctrl-X. I've hit this at least 5 times across sessions.
Root cause is a path/cwd mismatch for background sessions that were relocated into a git worktree: the conversation transcript is stored under the worktree's encoded project directory, but the background job that the agents view lists records cwd/originCwd as the parent repository. On reopen, the worker is launched with cwd = parent repo and resumes by explicit session ID; the conversation set it loads is scoped to the parent-repo project and does not contain the worktree transcript, so the explicit-ID lookup fails (failure_reason: "not_found_explicit_id") and the worker calls process.exit(1).
This is closely related to #5768 (resume only works from the directory where the session was started) — it's a specific, crash-looping instance of that cwd-keyed-resume family — and adjacent to #57920 (worktree transcript not found on --resume), but distinct: the worktree here is live (not removed), and the failure surfaces via the agents view with a worker crash/respawn loop rather than a one-shot CLI error.
Environment
- Claude Code 2.1.162 (macOS)
- The affected job record was created under 2.1.161; the bug still reproduces on 2.1.162.
- Sessions were relocated into git worktrees under
<repo>/.claude/worktrees/<branch>/(a worktree-per-task workflow).
Steps to reproduce
- From a repo root (e.g.
/Users/USER/dev/myrepo), launch/spawn a background agent session. - During that session, relocate it into a git worktree (e.g.
/Users/USER/dev/myrepo/.claude/worktrees/<branch>/). The transcript is now written under the project directory encoded from the worktree path. - Let the job reach a terminal state (
state: "done"). - Later, from the agents view, select that session to reopen it.
Expected: the conversation resumes (the transcript exists and is valid).
Actual: No conversation found with session ID: <uuid> → [worker crashed (exit 1) — respawning…], repeated ~4× before returning to the agents view. The entry persists in the list until removed with Ctrl-X.
Evidence / diagnosis
For one crashing session (<uuid>):
The transcript exists and is valid — only under the worktree-encoded project dir:
$ find ~/.claude/projects -name '<uuid>.jsonl'
~/.claude/projects/-Users-USER-dev-myrepo--claude-worktrees-<branch>/<uuid>.jsonl # valid, hundreds of lines
# NOT present under the parent-repo project dir, which is where a cwd-scoped lookup searches:
$ ls ~/.claude/projects/-Users-USER-dev-myrepo/<uuid>.jsonl
No such file or directory
Note the -- in myrepo--claude-worktrees: the project-dir encoding maps both / and . to -, so /myrepo/.claude/worktrees → -myrepo--claude-worktrees. The worktree itself is live — git worktree list shows the branch checked out there. This is not a deleted-worktree problem.
The job record that backs the agents-view entry — ~/.claude/jobs/<jobid>/state.json — has the mismatch:
{
"state": "done",
"resumeSessionId": "<uuid>",
"linkScanPath": "/Users/USER/.claude/projects/-Users-USER-dev-myrepo--claude-worktrees-<branch>/<uuid>.jsonl",
"cwd": "/Users/USER/dev/myrepo", // parent repo, NOT the worktree
"originCwd": "/Users/USER/dev/myrepo",
"cliVersion": "2.1.161"
}
The job already stores the correct transcript location in linkScanPath, but cwd/originCwd point at the parent repo. The resume path keys off cwd + explicit session ID, not linkScanPath.
The failing code path (from the bundle; 2.1.160/2.1.161 shown, logic unchanged in 2.1.162):
{entrypoint:"cli_flag", success:!1, failure_reason:"not_found_explicit_id"});
let rT = `No conversation found with session ID: ${id}`;
return V(rT,{level:"error"}), await exit1Fn(); // exit1Fn === process.exit(1)
exit(1) is the "worker crashed (exit 1)"; the supervisor respawns, re-attempts the same explicit ID against the same (wrong) cwd-scoped conversation set, and exits 1 again — producing the bounded respawn loop.
This is a class, not a one-off. Scanning all background jobs with a state.json, multiple carry the same signature (transcript/linkScanPath under a *worktrees* project dir while cwd/originCwd is the parent repo). I've personally had to Ctrl-X / kill at least 5 such entries across sessions.
Two observations bundled here
- The crash loop on reopen (primary): cwd-scoped explicit-ID resume can't find a transcript that lives under the worktree project dir.
- The entry isn't dismissed by the crash — it persists in the agents view because it is a
state: "done"job directory at~/.claude/jobs/<jobid>/; the crash doesn't update or remove that backing record, so onlyCtrl-Xclears it. Arguably the worker should notexit(1)(taking down the TUI worker) on a recoverable "conversation not found" — it should surface a non-fatal error and leave the agents view responsive.
Suggested fixes (any one breaks the loop)
- On resume, resolve the conversation from the job's
linkScanPath(or search across all project dirs by session ID) instead of only the cwd-derived project dir; or record the worktree path as the job'scwd/originCwdwhen the session is relocated into a worktree, so respawn launches the worker in the directory whose project dir holds the transcript. - Make "conversation not found" non-fatal in the worker: show an error and return to the agents view instead of
process.exit(1)+ respawn. This caps the blast radius even if the lookup still misses. - When a reopen permanently fails, mark/prune the agents-view entry so the user isn't forced to
Ctrl-Xevery stale row.
Workaround (for affected users)
The transcript is intact; only the resume path is broken. Re-launching claude from inside the worktree directory itself (so cwd matches the transcript's project dir) and resuming by ID works. The crash itself is harmless — it can't corrupt the transcript — but the entry must be cleared from the agents view with Ctrl-X.
Related
- #5768 — resume only works from the directory where the session was started (the underlying cwd-keyed-resume family).
- #57920 — worktree transcript not found on
--resumeafter the worktree path is removed (similar mechanism; that one requires the dir to be removed and has no crash loop / agents-view surface). - #60106 — agents-view orphans a session via clear(new)/clear(reset) (agents-view-orphan surface, no worktree, no crash loop).
4 Comments
Found 2 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
#5768 is the closest possible duplicate, but if it is a duplicate it would be an implementation detail unknown to me. I'm not using
--resumedirectly. It's possible that agent view is, I don't know for sure.This is a path/cwd mismatch -- the job record has
cwdpointing at the parent repo, but the transcript was written under the worktree project dir (~/.claude/projects/-Users-...-worktrees-<branch>/). Resume keyed off cwd + session ID fails to find it.Short-term workaround while this gets fixed:
/session save <name>to write a stable pointer before the crash window.~/.claude/projects/and find the worktree-scoped directory manually. The transcript JSONL is there. You can force-resume withclaude --resume <session-id> --project-dir <worktree-path>from inside the worktree directory.Ctrl-Xdismiss is the only current escape hatch from the stale agents view entry -- confirm that is working for you.The root fix is the resume logic should use
linkScanPathfrom the job record instead of reconstructing the path fromcwd+ session ID. That is clearly the intent givenlinkScanPathis already stored correctly.Same root cause, two additions from a VS Code extension repro: a different trigger/surface, and a failure mode that turns this from "cannot reopen" into "permanently stranded".
Variant: extension tab pinned to launch cwd across a restart
The trigger here is
EnterWorktreerather than a background-agent relocation, and the surface is the VS Code extension rather than the agents view.EnterWorktree, moving cwd to~/worktrees/<repo>/<branch>The extension stores the tab against the cwd the session launched from and replays that pair on cold start. From the extension log:
Note that the single lookup failure cascades into
Failed to load config cacheandError processing client request.While the extension host stays alive it holds the channel in memory, so the mismatch is invisible. Only a cold restart replays the stored
resume+cwdpair, which is why this reads to users as "restarting VS Code loses my session".The split-transcript symptom from #78940 also reproduces here: the
custom-titlerecord went to the launch-cwd project dir as a 119-byte stub while the body (3.5 MB) went to the worktree-encoded dir.The part that is not yet reported: worktree deletion makes it permanent
Every existing report in this family assumes the worktree still exists, so the transcript is at least reachable by
cd-ing back into it. In a worktree-per-task workflow the worktree is routinely removed after merge, and at that point:--resume <id>cannot be made to work from anywhere, because the only cwd that resolves to that project dir no longer exists,On one machine this had silently accumulated 13 orphaned worktree-scoped project dirs holding 18 transcripts (~16.5 MB), 7 of them substantive multi-MB sessions, with no user-visible indication that anything had been retained.
Recovery is possible but requires knowing the encoding scheme: a project dir name is the cwd with every non-alphanumeric character replaced by
-, which is lossy and one-way, so a stranded dir name cannot be decoded back to a path. The transcript's owncwdfield is the only reliable source. Copying the transcript into the parent repo's project dir restores it to the session list.One caveat for anyone auditing their own machine for this: transcripts whose records all carry
"isSidechain":trueare subagent runs, not resumable conversations. They are expected to fail an explicit-ID resume and are not evidence of this bug.Suggestion
Beyond fixing the cwd rebinding, a session whose recorded cwd no longer exists is currently invisible rather than merely broken. Falling back to the parent repo, or surfacing such sessions in the picker with their recorded cwd, would make the data recoverable without reverse-engineering the directory naming.
Happy to supply full logs if useful.