Hmbown/CodeWhale · error
Steer settlement task ended before acknowledgement
Error message
Steer settlement task ended before acknowledgement
What it means
Thrown when the steer settlement task's oneshot channel closes with an error before delivering an acknowledgement — i.e. the spawned settlement task terminated (panicked, was cancelled, or its sender was dropped) without reporting either Accepted or Dropped. The caller cannot know whether the steer was delivered.
Solutions
- Retry the steer after confirming the thread's engine is healthy and the turn is still InProgress.
- Check runtime logs for a panic or cancellation in the settlement task; if it is a recurring crash, fix or report the task bug.
- As a safe fallback, start a new turn with the steering message rather than assuming it was delivered.
Example fix
// before
let turn = runtime.steer(thread_id, turn_id, item).await?;
// after
match runtime.steer(thread_id, turn_id, item).await {
Ok(turn) => turn,
Err(e) if e.to_string().contains("settlement task ended") => {
// unknown delivery: safest is a fresh turn with the same item
runtime.start_turn(thread_id, item).await?
}
Err(e) => return Err(e),
} Defensive patterns
Strategy: try-catch
Try / catch
match runtime.steer(thread_id, turn_id, item).await {
Err(e) if e.to_string().contains("settlement task ended") => {
log::error("steer settlement channel closed; delivery unknown");
runtime.start_turn(thread_id, item).await // safe fallback
}
other => other,
} Prevention
- Monitor runtime logs for settlement task panics or cancellation during shutdown.
- Avoid tearing down the runtime while steer settlements are pending.
- Treat unknown steer delivery as not-delivered and resend as a new turn.
When it happens
Trigger: The `settle_rx` receiver gets `Err(_)` from the oneshot channel during the STEER_SETTLE_WAIT await — the settlement task ended abnormally (panic in the task, task aborted during shutdown, sender dropped without sending).
Common situations: Runtime shutdown or engine teardown cancels the settlement task mid-await; a bug or panic inside the settlement task; thread engine replaced while settlement was pending.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Compaction lifecycle task ended before acknowledgement
- Thread engine changed while preparing steer; retry
- Turn lifecycle task ended before acknowledgement
- Turn is no longer in progress and cannot be steered
- Turn moved on before the steer reached the model;
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/a1c09d680cb2632c.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_threads.rs:10495
// engine and durable record agree even if the API caller drops.
let outcome_rx = permit.send_with_outcome(prompt.clone());
touch_lru(&mut active.lru, thread_id);
queued_turn = turn.clone();
self.spawn_steer_settlement(turn, item, prompt, outcome_rx)
};
// The settler owns the verdict; this is only an observation of it.
// Steering a streaming turn settles in milliseconds, so the caller
// almost always gets the truth in-band. Behind a long tool call the
// engine will not look at its mailbox for minutes, and an API request
// must not hang that long — so the wait is bounded and the caller
// falls back to the honest `Queued` receipt it already has.
match tokio::time::timeout(STEER_SETTLE_WAIT, settle_rx).await {
Ok(Ok((SteerOutcome::Accepted, turn))) => Ok(turn),
Ok(Ok((SteerOutcome::Dropped, _))) => bail!(
"Turn {turn_id} moved on before the steer reached the model; {STEER_DROPPED_REASON}"
),
Ok(Err(_)) => bail!("Steer settlement task ended before acknowledgement"),
Err(_) => Ok(queued_turn),
}
}
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?;
View on GitHub (pinned to 73e0f67d83)