fix(coding-agent): detach hard-aborted refs so ensureLive can't route into a dead session
Preserving aborted refs on dispose exposed a latent invariant break: the executor's hard-abort path (finalizeSubagentLifecycle) set status `aborted` and disposed the session without detaching it. With the ref now retained, it kept a dangling pointer to the disposed session, and ensureLive returns any non-null ref.session before its revivability check — so hub focus / transcript chat could route into a dead session. - finalizeSubagentLifecycle: detach the session before disposing on the terminal hard-abort path, upholding the AgentRef invariant (session === null when aborted). - release(tombstone): detach before dispose too (capture the live session first), same invariant. - unregisterUnlessParked: preserve `aborted` refs only when already detached; an aborted ref still holding a live session is a bug and is unregistered rather than kept reachable. - Regression test now asserts ensureLive rejects a tombstoned id as terminal. Fixes #7250
This commit is contained in:
@@ -2364,7 +2364,14 @@ export async function finalizeSubagentLifecycle(args: {
|
||||
const resumableAbort =
|
||||
args.abortKind === "budget" && args.keepAlive && !args.isolated && args.reviveSession !== null;
|
||||
if (args.aborted && !resumableAbort) {
|
||||
if (ref && ownsRef) registry.setStatus(args.id, "aborted", ref);
|
||||
if (ref && ownsRef) {
|
||||
// Terminal hard kill: mark `aborted` and detach the session before
|
||||
// disposing so the ref satisfies the AgentRef invariant (session null
|
||||
// when aborted) — ensureLive/hub focus must treat it as terminal, never
|
||||
// route into the disposed session.
|
||||
registry.setStatus(args.id, "aborted", ref);
|
||||
registry.detachSession(args.id, ref);
|
||||
}
|
||||
await disposeSession();
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user