What happened
After PR #1604 merged (squash), its worktree lane could not be reaped. The lane (mobile-repo-line) was clean, fully landed, its remote branch deleted, nothing running — but manage_worktree remove --force refused:
manage_worktree remove: live work under <lane>: 034Q0FmS… (subagent, retained — idle), 034Q0Fpx2… (subagent, retained — idle), 034Q0FtI… (subagent, retained — idle), 034Q0FwG… (subagent, retained — idle)
The four blockers were the simplify-code review delegates: read-only, shared-workspace subagents (no isolation="worktree"), completed, and stopped via job_stop. Their retained sessions list the lane as their working directory, and that is the whole blocker.
Repro
- A root session creates a managed lane (
manage_worktree create) and enters it.
- It spawns shared-workspace delegates (the default) whose cwd is the lane; they complete their task and go retained-idle.
job_stop them — the stop reports already_idle · stopped_by_parent, and the sessions stay retained (resumable terminal history).
- Exit the lane; land its work (e.g. squash-merge a PR and delete the remote branch), so the lane is clean and merged.
manage_worktree remove <name> --delete-branch --force refuses on live work, with no dispose hint and no force cascade.
Root cause
The refusal is correct per the guard's design; the dead-end is in the cascade's coverage:
liveWorkUnder (agent/session_tools_worktree.go, ~L885) labels stopped shared-workspace children (subagent, retained — idle) — deliberately honest, so a model removing a clean merged lane gets a signal it can act on.
retainedIdleDelegateIDs (~L1010) resolves those blockers to disposable delegates through delegateController.ownedStableWorktreeSnapshots — a map that only worktree-isolated delegates appear in. A shared-workspace child has no stable worktree descriptor, so resolution fails closed (ok=false).
- Both the dispose hint (
disposeHintForRetainedIdle, ~L1043, returns "" when resolution fails → generic refusal) and remove's force cascade (~L2255: forceCascadeDlgIDs is only recorded when retainedIdleDelegateIDs(live) succeeds) therefore never arm for shared-workspace children.
So the "legitimate way forward" the retained-idle label exists to provide (the comment at ~L970: labeling such a child "running" would "dead-end a model trying to remove a clean, merged lane") does not exist for this configuration. The label was fixed; the shared-workspace case still dead-ends.
What does not work (verified this session)
job_stop does not de-retain: an idle delegate's stop is already_idle, and retained means resumable history, by design. No exposed tool deletes retained session state; doctor_evener is read-only.
- Admission-time reclamation (
max_retained_terminal) is capacity-driven only. A minimal probe delegate admission did not trigger it; the four subtrees stayed retained.
manage_worktree dispose only retires a delegate's own isolation lane; shared-workspace children have none.
The lane stays unreapable until capacity-driven reclamation happens to remove those quiescent retained subtrees, after which remove/prune succeeds. The lane is inert and harmless in the meantime — this is a hygiene dead-end, not a correctness bug.
Fix direction
The guard's fail-closed posture is right for other sessions' lanes and foreign children. The gap is the caller's own lane hosting the caller's own shared-workspace children. Suggested: let the force cascade resolve a blocker that is
- owned by the calling session,
- retained-idle (not running or driving),
- shared-workspace, and
- whose recorded working directory is under the lane being removed,
and dispose it through the same sanctioned cascade (the child holds no live work; its lane is the target itself, owned by the caller). Everything else — running shells, driving children, other sessions' lanes — keeps failing closed exactly as today.
Two side observations from the same session, no action implied:
manage_worktree's repeated-identical-call fuse blocked the third identical remove attempt and forced an approach change — that worked as intended.
git merge on this host needs an explicit --no-ff (merge.ff=only is set repo-wide); worth knowing when bringing PR branches up to date under strict branch protection.
What happened
After PR #1604 merged (squash), its worktree lane could not be reaped. The lane (
mobile-repo-line) was clean, fully landed, its remote branch deleted, nothing running — butmanage_worktree remove --forcerefused:The four blockers were the
simplify-codereview delegates: read-only, shared-workspace subagents (noisolation="worktree"), completed, and stopped viajob_stop. Their retained sessions list the lane as their working directory, and that is the whole blocker.Repro
manage_worktree create) and enters it.job_stopthem — the stop reportsalready_idle · stopped_by_parent, and the sessions stay retained (resumable terminal history).manage_worktree remove <name> --delete-branch --forcerefuses on live work, with no dispose hint and no force cascade.Root cause
The refusal is correct per the guard's design; the dead-end is in the cascade's coverage:
liveWorkUnder(agent/session_tools_worktree.go, ~L885) labels stopped shared-workspace children(subagent, retained — idle)— deliberately honest, so a model removing a clean merged lane gets a signal it can act on.retainedIdleDelegateIDs(~L1010) resolves those blockers to disposable delegates throughdelegateController.ownedStableWorktreeSnapshots— a map that only worktree-isolated delegates appear in. A shared-workspace child has no stable worktree descriptor, so resolution fails closed (ok=false).disposeHintForRetainedIdle, ~L1043, returns""when resolution fails → generic refusal) and remove's force cascade (~L2255:forceCascadeDlgIDsis only recorded whenretainedIdleDelegateIDs(live)succeeds) therefore never arm for shared-workspace children.So the "legitimate way forward" the retained-idle label exists to provide (the comment at ~L970: labeling such a child "running" would "dead-end a model trying to remove a clean, merged lane") does not exist for this configuration. The label was fixed; the shared-workspace case still dead-ends.
What does not work (verified this session)
job_stopdoes not de-retain: an idle delegate's stop isalready_idle, and retained means resumable history, by design. No exposed tool deletes retained session state;doctor_eveneris read-only.max_retained_terminal) is capacity-driven only. A minimal probe delegate admission did not trigger it; the four subtrees stayed retained.manage_worktree disposeonly retires a delegate's own isolation lane; shared-workspace children have none.The lane stays unreapable until capacity-driven reclamation happens to remove those quiescent retained subtrees, after which
remove/prunesucceeds. The lane is inert and harmless in the meantime — this is a hygiene dead-end, not a correctness bug.Fix direction
The guard's fail-closed posture is right for other sessions' lanes and foreign children. The gap is the caller's own lane hosting the caller's own shared-workspace children. Suggested: let the force cascade resolve a blocker that is
and dispose it through the same sanctioned cascade (the child holds no live work; its lane is the target itself, owned by the caller). Everything else — running shells, driving children, other sessions' lanes — keeps failing closed exactly as today.
Two side observations from the same session, no action implied:
manage_worktree's repeated-identical-call fuse blocked the third identicalremoveattempt and forced an approach change — that worked as intended.git mergeon this host needs an explicit--no-ff(merge.ff=onlyis set repo-wide); worth knowing when bringing PR branches up to date under strict branch protection.