Repro
Spawn a session, let the agent crash or exit without acknowledging (reportWatcherActiveTrigger: no_acknowledge). The lifecycle manager probes, sees signal_disagreement between the native activity signal (activity=exited) and the initial runtime probe (runtime=alive), runs through 50 detecting attempts, escalates, and parks the session at state: stuck, reason: probe_failure. The runtime probe eventually updates to exited, but the session-level state never advances.
Observed state (real session ao-165)
"lifecycle": {
"session": { "state": "stuck", "reason": "probe_failure", "terminatedAt": null,
"lastTransitionAt": "2026-05-19T13:14:54.490Z" },
"runtime": { "state": "exited", "reason": "process_missing",
"lastObservedAt": "2026-05-19T13:14:54.490Z" },
"pr": { "state": "none", "reason": "not_created" }
},
"status": "stuck",
"detectingAttempts": "50",
"detectingEscalatedAt": "2026-05-19T12:27:51.146Z",
"lifecycleEvidence": "signal_disagreement runtime=alive process=dead activity_signal=valid via_native activity=exited",
"reportWatcherActiveTrigger": "no_acknowledge",
"reportWatcherTriggerCount": "41"
The agent process is gone, the runtime is gone, no PR was ever created — but the session is not terminated.
Why this is a bug
The sidebar visibility gate in packages/web/src/components/ProjectSidebar.tsx:454-457 only hides sessions whose getAttentionLevel(session) === \"done\". A stuck lifecycle state routes to respond in packages/web/src/lib/types.ts:504-512:
if (session.lifecycle?.sessionState === \"stuck\" || ...) return \"respond\";
So the session sits on the sidebar with an orange dot forever. Neither the Show killed nor the Show done toggle helps — they only gate done-level sessions. The only ways to clear it today:
ao kill <id> (manual)
- Click Restore (spawns a new runtime, leaves the record)
rm the JSON by hand
That's not great UX for a session the lifecycle manager has already concluded is unrecoverable.
Proposed fix (core, not surface)
When resolveProbeDecision (in packages/core/src/lifecycle-manager.ts) determines:
runtime.state === \"exited\" or \"missing\", and
- native activity signal is
exited, and
- no live PR depends on the runtime (
pr.state ∈ {none, closed_unmerged}),
…it should promote session.state from stuck → terminated with reason: probe_failure (or a new agent_process_exited reason). deriveLegacyStatus already maps error_in_process / probe_failure → errored, which is in the done bucket, so the sidebar would then hide it by default once showDone is off.
Today the lifecycle treats probe_failure as "I can't tell, escalate to a human". But when every signal agrees the agent is gone, there's nothing to escalate — just terminate it.
Out of scope (separate concerns)
File:line index
- Visibility gate:
packages/web/src/components/ProjectSidebar.tsx:454-457
\"stuck\" → \"respond\" mapping: packages/web/src/lib/types.ts:504-512
isDashboardSessionDone: packages/web/src/lib/types.ts:392-410
deriveLegacyStatus terminal-reason mapping: packages/core/src/lifecycle-state.ts:444-460
- Probe decision pipeline:
packages/core/src/lifecycle-manager.ts (resolveProbeDecision)
Repro
Spawn a session, let the agent crash or exit without acknowledging (
reportWatcherActiveTrigger: no_acknowledge). The lifecycle manager probes, seessignal_disagreementbetween the native activity signal (activity=exited) and the initial runtime probe (runtime=alive), runs through 50 detecting attempts, escalates, and parks the session atstate: stuck, reason: probe_failure. The runtime probe eventually updates toexited, but the session-level state never advances.Observed state (real session
ao-165)The agent process is gone, the runtime is gone, no PR was ever created — but the session is not terminated.
Why this is a bug
The sidebar visibility gate in
packages/web/src/components/ProjectSidebar.tsx:454-457only hides sessions whosegetAttentionLevel(session) === \"done\". Astucklifecycle state routes torespondinpackages/web/src/lib/types.ts:504-512:So the session sits on the sidebar with an orange dot forever. Neither the Show killed nor the Show done toggle helps — they only gate
done-level sessions. The only ways to clear it today:ao kill <id>(manual)rmthe JSON by handThat's not great UX for a session the lifecycle manager has already concluded is unrecoverable.
Proposed fix (core, not surface)
When
resolveProbeDecision(inpackages/core/src/lifecycle-manager.ts) determines:runtime.state === \"exited\"or\"missing\", andexited, andpr.state ∈ {none, closed_unmerged}),…it should promote
session.statefromstuck→terminatedwithreason: probe_failure(or a newagent_process_exitedreason).deriveLegacyStatusalready mapserror_in_process/probe_failure→errored, which is in the done bucket, so the sidebar would then hide it by default onceshowDoneis off.Today the lifecycle treats
probe_failureas "I can't tell, escalate to a human". But when every signal agrees the agent is gone, there's nothing to escalate — just terminate it.Out of scope (separate concerns)
File:line index
packages/web/src/components/ProjectSidebar.tsx:454-457\"stuck\"→\"respond\"mapping:packages/web/src/lib/types.ts:504-512isDashboardSessionDone:packages/web/src/lib/types.ts:392-410deriveLegacyStatusterminal-reason mapping:packages/core/src/lifecycle-state.ts:444-460packages/core/src/lifecycle-manager.ts(resolveProbeDecision)