Hmbown/CodeWhale · critical
engine event channel closed before turn
Error message
engine event channel closed before turn {turn_id} completed What it means
During a turn the worker awaits events from the engine on a channel. If the channel closes (all engine senders dropped) before the turn finished and no interrupt was observed, the turn can never complete, so the worker bails with this error. It converts a silent hang into an explicit failure.
Solutions
- Investigate why the engine task exited (panic logs, shutdown path) before the turn finished
- Retry the turn after restarting the engine/runtime session
- Check for explicit shutdown calls (drop of engine handle) racing in-flight turns and serialize them
Example fix
// before
let replay = thread.start_turn(turn_id).await?; // engine may already be gone
// after
if thread.engine_alive().await {
thread.start_turn(turn_id).await?;
} else {
thread.restart_engine().await?;
thread.start_turn(turn_id).await?;
} Defensive patterns
Strategy: retry
Validate before calling
if !engine_handle.is_alive() { engine.restart().await?; } Try / catch
loop {
match thread.run_turn(turn_id).await {
Err(e) if e.to_string().contains("engine event channel closed") => {
engine.restart().await?;
continue; // with backoff
}
other => break other,
}
} Prevention
- Keep a strong handle to the engine sender for the turn's lifetime
- Log engine task panics so channel closure is diagnosable
- Serialize shutdown with in-flight turns
When it happens
Trigger: The engine task is dropped/panicked or its event sender is closed while awaiting the next event for turn_id, and the interrupt check (which would set RuntimeTurnStatus::Interrupted) did not fire.
Common situations: Engine crash or panic mid-turn; runtime shutdown while a turn is in flight; a bug dropping the engine handle prematurely; process teardown racing an active turn.
Related errors
- Failed to start turn: engine operation channel closed
- Runtime engine shutdown failed
- Failed to sync thread session
- Failed to trigger compaction: engine operation channel…
- parent cancellation fan-in
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/862d68bc91986d23.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_threads.rs:11576
loop {
let event = if let Some(event) = pending_event.take() {
Some(event)
} else if event_channel_closed {
None
} else {
let mut rx = engine.rx_event.write().await;
rx.recv().await
};
let Some(event) = event else {
if self
.is_interrupt_requested(&thread_id, &turn_id)
.await
.unwrap_or(false)
{
turn_status = Some(RuntimeTurnStatus::Interrupted);
break;
}
bail!("engine event channel closed before turn {turn_id} completed");
};
// SyncSession and configuration operations emit control status
// receipts on the same channel before SendMessage is processed.
// They belong to engine setup, not to the next claimed turn.
if !saw_turn_started
&& matches!(
&event,
EngineEvent::Status { .. }
| EngineEvent::McpSessionBoot { .. }
| EngineEvent::SessionUpdated { .. }
| EngineEvent::AgentList { .. }
| EngineEvent::AgentSpawned { .. }
| EngineEvent::AgentProgress { .. }
| EngineEvent::AgentComplete { .. }
| EngineEvent::SubAgentMailbox { .. }
)
{View on GitHub (pinned to 73e0f67d83)