Hmbown/CodeWhale · error
Steer receipt task ended before acknowledgement
Error message
Steer receipt task ended before acknowledgement
What it means
After enqueueing a steering prompt, the runtime awaits a receipt channel fed by a spawned acknowledgement task (`spawn_steer_receipts`). If that task terminates (panics, early return, cancellation) without sending an acknowledgement, the receiver returns a RecvError and this anyhow error is produced. It means the steering prompt was delivered to the engine but its receipt/ack was never observed, so the caller cannot confirm the steer landed.
Solutions
- Retry the steer after confirming the thread's turn is still active
- Check logs for a panic or early return inside the spawn_steer_receipts task
- Ensure the engine/thread is not being closed concurrently with the steer call
Example fix
// before
let receipt = receipt_rx.await.map_err(|_| anyhow!("Steer receipt task ended before acknowledgement"))?;
// after
let receipt = match receipt_rx.await {
Ok(r) => r,
Err(_) => return Err(anyhow!("steer receipt task ended before acknowledgement; is the turn still active?")),
}; Defensive patterns
Strategy: retry
Validate before calling
// confirm the thread has an active turn before steering
if !thread_has_active_turn(thread_id) { return Err("cannot steer: no active turn"); } Type guard
fn receipt_alive(rx: &ReceiptRx) -> bool { !rx.is_closed() } Try / catch
match steer(thread_id, prompt) {
Err(e) if e.to_string().contains("Steer receipt task ended") => retry_steer_with_backoff(thread_id, prompt, 2),
other => other?,
} Prevention
- Only steer while the turn is confirmed active
- Avoid closing/shutting down the engine concurrently with steer calls
- Retry transient receipt failures with a short backoff
When it happens
Trigger: Calling the steer API on an active thread when the spawned receipt task exits before `receipt_rx` resolves — e.g. the task panics, the turn ends and the task is dropped, or the task returns early on an unexpected engine state.
Common situations: Steering a thread while the engine is concurrently shutting down or dropping the turn; a panic inside the receipt task; race between turn completion and steer submission.
Related errors
- Failed to steer turn
- Pet recorder is closed or already writing.
- A saved trace identifier collided. Try saving again.
- a window change is already in progress
- another Codewhale process
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/c4c2220a79d5ac47.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_threads.rs:9653
Err(error) => {
let cleanup = self.store.remove_item(&item.id);
return match cleanup {
Ok(()) => Err(error),
Err(cleanup_error) => Err(anyhow!(
"Failed to persist steer: {error}; cleanup also failed: {cleanup_error}"
)),
};
}
};
// The reserved send has no await/failure point. From here the
// engine and durable record agree even if the API caller drops.
permit.send(prompt.clone());
touch_lru(&mut active.lru, thread_id);
self.spawn_steer_receipts(turn, item, prompt)
};
receipt_rx
.await
.map_err(|_| anyhow!("Steer receipt task ended before acknowledgement"))
}
pub async fn compact_thread(
&self,
thread_id: &str,
req: CompactThreadRequest,
) -> Result<TurnRecord> {
// Compaction carries a concrete provider route just like a normal
// turn. Keep the same reload/admission boundary through durable engine
// handoff so it cannot dispatch an old credential or endpoint after a
// successful config reload.
let _config_admission = self.config_admission.read().await;
self.ensure_accepting_execution()?;
let thread = self.get_thread(thread_id).await?;
let engine = self.ensure_engine_loaded(&thread).await?;
let client_preflight_required = {
let active = self.active.lock().await;View on GitHub (pinned to 433685b202)