Found while testing checkpoint revert.
An agent requests approval, the client that would answer goes away — killed,
crashed, closed the laptop — and the session sits running forever. Nothing
times out and nothing notices.
Concretely, from a real session's log:
turn.started
checkpoint.captured
tool.call.began · approval.requested · approval.resolved · tool.call.ended
tool.call.began · approval.requested ← nobody ever answered
(no turn.completed)Why it matters beyond being stuck
A wedged turn is not just idle, it blocks recovery: session.checkpoint.revert
is deliberately refused while a turn is running, so the one action that would
undo the agent's half-finished work is unavailable exactly when you want it.
Stop settles it — the button exists and works, and clicking it moved the
session to idle and revealed the revert control. But a turn should not depend
on a person noticing that it is stuck.
Options
- A timeout on an unanswered approval, defaulting to deny. Simple, and denial is the safe direction, but it punishes someone who stepped away for coffee.
- Deny pending approvals when the last subscriber for a session disconnects. Wrong on its own — the desktop window and a phone are both legitimate clients and either may reconnect.
- Surface the pending approval as session state (
awaiting-approvalalready exists inSessionState['state']and nothing sets it), so any client that connects sees there is a decision waiting and can answer it. This is probably the right one: it makes the state visible rather than guessing at intent.
The third option also fixes a smaller thing: awaiting-approval is declared in
the projection today and never set, so the UI cannot distinguish "thinking" from
"waiting for you".