Hmbown/CodeWhale · error
turn monitor exits normally only after assigning a terminal…
Error message
turn monitor exits normally only after assigning a terminal status
What it means
Panic from `turn_status.expect(...)` at the end of the turn monitor loop. The monitor's exit paths should always assign a terminal status before breaking out of the select loop; reaching the expect with None means the loop ended without ever producing a terminal turn status, indicating an unhandled event path or premature loop exit.
Solutions
- Add a terminal-status assignment to every loop-exit path (including channel closure)
- Log unmatched select branches so silent exits become visible
- Replace the bare expect with an explicit error status fallback for unexpected exits
Example fix
// before
let mut turn_status = turn_status
.expect("turn monitor exits normally only after assigning a terminal status");
// after
let turn_status = turn_status.unwrap_or_else(|| {
tracing::error!("turn monitor exited without terminal status");
TerminalStatus::error("turn monitor exited without terminal status")
}); Defensive patterns
Strategy: validation
Type guard
if turn_status.is_none() { tracing::error!("monitor exited without terminal status"); turn_status = Some(TerminalStatus::error("unexpected exit")); } Try / catch
let turn_status = turn_status.unwrap_or_else(|| TerminalStatus::error("turn monitor exited without terminal status")); Prevention
- Enumerate all loop-exit paths and require a terminal status in each
- Treat channel closure / cancellation as terminal-status-producing events
- Add a monitor-level test that every exit path yields a status
When it happens
Trigger: The turn monitor's select loop terminating via a channel close, cancellation, or an unmatched event branch without any branch setting `turn_status` to a Terminal variant.
Common situations: Upstream stream ends or errors in a way not mapped to a terminal status; a refactor removing an assignment path in the monitor's match arms.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- event recovery buffer fits u64
- event transaction runs once
- first coordination interrupt
- parent cancellation fan-in
- provider authority checked above
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/7f7de60ed353f671.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_threads.rs:12888
// only when the store still has none — a concurrent
// PUT/DELETE is the newer revision and wins — and
// adopt the revision only when we actually created it,
// so settlement and later checkpoints stay fenced.
if let Some(goal_id) = self
.store
.create_goal_from_snapshot_if_absent(&thread_id, &snapshot)?
{
admitted_goal_id = Some(goal_id);
}
}
latest_goal_snapshot = Some(snapshot);
}
_ => {}
}
}
let mut turn_status = turn_status
.expect("turn monitor exits normally only after assigning a terminal status");
if self
.is_interrupt_requested(&thread_id, &turn_id)
.await
.unwrap_or(false)
{
turn_status = RuntimeTurnStatus::Interrupted;
}
if let Some(mut item) = current_message_item.take() {
item.status = match turn_status {
RuntimeTurnStatus::Completed => TurnItemLifecycleStatus::Completed,
RuntimeTurnStatus::Interrupted | RuntimeTurnStatus::Canceled => {
TurnItemLifecycleStatus::Interrupted
}
RuntimeTurnStatus::Queued
| RuntimeTurnStatus::InProgress
| RuntimeTurnStatus::Failed => TurnItemLifecycleStatus::Failed,View on GitHub (pinned to 73e0f67d83)