Every stop signal for a Playbook run is gated on the run having been seen running:
SessionState::Done | SessionState::Errored => {
if run.seen_running {
clear = true;
}
}
SessionState::AwaitingInput => {
if run.seen_running && (!run.agent_managed || (…pending empty…)) {
clear = true;
}
}
(crates/daemon/src/session/playbook_run.rs ~lines 541–568 @ ad47152)
seen_running is only set from a SessionState::Running transition. A turn that ends before the busy detector ever reports Running never sets it, so no stop signal can fire and the run survives to the PLAYBOOK_RUN_MAX_MS backstop — 10 minutes of the whole document shimmering (crates/daemon/src/session.rs ~line 122).
The Done | Errored arm is the worse half: a terminal session can never settle anything, so there is nothing to protect by waiting.
Repro
A — fast turn, no Running transition. Run a Playbook on a shell session whose delivered prompt fails immediately (e.g. bash syntax error). Subscribed to subscribe.events for 20s afterwards: one playbook/state with seen_running: false, and no session/state status event at all. The session sits at awaiting input while all 9 blocks report shimmer: true, and stay that way.
B — session dies mid-run. Create a session, set a Playbook, playbook.execute, then kill the session:
=== after kill ===
● ✗ se01e3332a shell errored
b0:0 shimmer=True '# T'
b1:0 shimmer=True '- alpha'
b2:0 shimmer=True '- beta'
The owning agent is gone and the Playbook keeps shimmering.
Both reproduce with a shell harness, but the shape is general: any harness whose turn fails fast (crashed CLI, auth error, rejected prompt) lands in case A.
Expected
Done / Errored: clear the run unconditionally. Nothing can settle those blocks.
AwaitingInput with no Running ever observed: use a short post-dispatch grace (~10–20s) rather than the 10-minute backstop, so a failed dispatch stops shimmering promptly.
Per spec 0042, narrowing/stop remains best-effort and system-owned; this is about the backstop being the only signal in these cases.
Acceptance criteria
Found during a hands-on UX audit of the Playbook in the TUI and web UI.
Every stop signal for a Playbook run is gated on the run having been seen running:
(
crates/daemon/src/session/playbook_run.rs~lines 541–568 @ ad47152)seen_runningis only set from aSessionState::Runningtransition. A turn that ends before the busy detector ever reports Running never sets it, so no stop signal can fire and the run survives to thePLAYBOOK_RUN_MAX_MSbackstop — 10 minutes of the whole document shimmering (crates/daemon/src/session.rs~line 122).The
Done | Erroredarm is the worse half: a terminal session can never settle anything, so there is nothing to protect by waiting.Repro
A — fast turn, no Running transition. Run a Playbook on a
shellsession whose delivered prompt fails immediately (e.g. bash syntax error). Subscribed tosubscribe.eventsfor 20s afterwards: oneplaybook/statewithseen_running: false, and nosession/statestatus event at all. The session sits atawaiting inputwhile all 9 blocks reportshimmer: true, and stay that way.B — session dies mid-run. Create a session, set a Playbook,
playbook.execute, then kill the session:The owning agent is gone and the Playbook keeps shimmering.
Both reproduce with a
shellharness, but the shape is general: any harness whose turn fails fast (crashed CLI, auth error, rejected prompt) lands in case A.Expected
Done/Errored: clear the run unconditionally. Nothing can settle those blocks.AwaitingInputwith no Running ever observed: use a short post-dispatch grace (~10–20s) rather than the 10-minute backstop, so a failed dispatch stops shimmering promptly.Per spec
0042, narrowing/stop remains best-effort and system-owned; this is about the backstop being the only signal in these cases.Acceptance criteria
Done/Erroredclears its active Playbook run regardless ofseen_running.Found during a hands-on UX audit of the Playbook in the TUI and web UI.