Background sessions dispatched from a non-git directory can never be deleted from the agents view: "worktree could not be removed (no git root for the worktree)"
Environment
- Claude Code 2.1.211 (native build, macOS arm64, Darwin 24.6); affected jobs were created on 2.1.210 and 2.1.211
- Background sessions dispatched from the agents view (
claude agents), default worktree isolation
Summary
If a background session is dispatched from a directory that is not a git repository but contains git repositories (a multi-repo workspace folder like ~/Code), and the session then enters a worktree of one of the nested repos (which background-session policy requires before editing, and which EnterWorktree explicitly supports for nested repos on first entry), the session can never be deleted from the agents view afterwards. Every delete attempt fails with:
not deleted · worktree could not be removed (no git root for the worktree)
The state is stable, so these undeletable rows accumulate — one per background job dispatched this way.
Steps to reproduce
cd ~/Code— a plain folder that is not a git repo, containing e.g.~/Code/repo-a(a git repo).- Open
claude agentsand dispatch a background session, e.g. "look into issue #123" (which the agent resolves againstrepo-a). - The agent calls
EnterWorktreebefore editing (as the background-session system prompt requires). Since the launch cwd is not a repo, the worktree is created for the nested repo:~/Code/repo-a/.claude/worktrees/<name>. The job'sstate.jsonrecordsworktreePathandworktreeBranch, whileoriginCwdremains~/Code. - Let the job run to completion (state
done, PR pushed, worktree clean). - Press delete (
x) on the session in the agents view.
Expected: worktree removed (it is clean, fully pushed, and its lock is held by a dead pid), session deleted.
Actual: not deleted · worktree could not be removed (no git root for the worktree), forever.
Root cause (from the 2.1.211 bundle)
In the job-deletion path (deleteJob), the git root used for worktree removal is resolved as:
let p = tp(r.originCwd ?? a) ?? void 0 // findGitRoot(state.originCwd ?? state.worktreePath)
The fallback to the worktree path only applies when originCwd is unset. When originCwd is set but does not resolve to a git repository (the multi-repo workspace case), p is undefined, and the removal helper hard-fails before doing anything:
async function removeAgentWorktree(e, t, r, n, o) {
...
if (!r) return log("Cannot remove agent worktree: no git root provided"),
{ outcome: "failed", errorSummary: "no git root for the worktree" };
...
}
which surfaces as keptReason: "remove_failed" → not deleted · worktree could not be removed (no git root for the worktree).
Notes:
- All the safety checks that could otherwise keep the worktree (dirty, unpushed, in-use, live lock) pass or are bypassed; the failure is purely the root resolution.
- The same permanent failure occurs when the worktree directory has already been deleted externally (the
git statusprobe errors, and the flow still reachesremoveAgentWorktreewith no git root). - The worktree creation path already computes and returns the git root (
{worktreePath, worktreeBranch, headCommit, gitRoot}), butgitRootis not persisted into the job'sstate.json, so deletion has to re-derive it — from the wrong directory.
Suggested fix
Any of:
- Fall back through candidates:
findGitRoot(originCwd) ?? findGitRoot(worktreePath) ?? deriveMainRepoFromGitdirPointer(worktreePath)(the.gitfile inside a linked worktree points at<main-repo>/.git/worktrees/<name>, which identifies the owning repo even whenoriginCwdis useless). - Persist
gitRootin the job'sstate.jsonat worktree creation time (it is already computed there) and use it at deletion time. This also covers the case where the worktree directory was deleted externally before the session was deleted.
Workaround
Edit ~/.claude/jobs/<jobid>/state.json and set originCwd to the root of the repository that owns the worktree (the path prefix of worktreePath before /.claude/worktrees/). The next delete from the agents view then proceeds through the normal cleanup path, including all its safety checks, and succeeds.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Fix incoming
This is biting me also on 2.1.212
Fixed for me after updating 2.1.220.