Hmbown/CodeWhale · error
Turn lifecycle task ended before acknowledgement
Error message
Turn lifecycle task ended before acknowledgement
What it means
Raised when the oneshot channel that carries the turn's acceptance acknowledgement is dropped before a value arrives, meaning the spawned turn-lifecycle task ended (panicked, was aborted, or exited early) without ever accepting or rejecting the prepared turn. The runtime converts the RecvError into this anyhow error instead of hanging forever.
Solutions
- Check the application logs/stderr for a panic in the turn lifecycle task around the time of this error.
- Retry the turn — the store is consistent, so a fresh submission is safe.
- If it reproduces, ensure the Tokio runtime is not being shut down (or the task aborted) while turns are in flight.
- Report as a bug if reproducible: the lifecycle task should always send an acceptance or a rejection.
Defensive patterns
Strategy: retry
Try / catch
match send_turn(prompt) {
Err(e) if e.to_string().contains("ended before acknowledgement") => {
// check logs for lifecycle-task panic, then resubmit the turn once
}
r => r?,
} Prevention
- Keep the Tokio runtime alive for the duration of in-flight turns.
- Watch logs for panics in turn lifecycle tasks and fix root causes promptly.
- Avoid force-killing the app while turns are being submitted.
- Ensure adequate memory so worker tasks are not OOM-killed.
When it happens
Trigger: Awaiting `acceptance_rx` after spawning the claimed-turn monitor; the sender side is dropped because the lifecycle task panicked or was cancelled before sending the turn record through.
Common situations: A panic inside the engine turn task (bug or OOM kill); the Tokio runtime shutting down while a turn is being submitted; the lifecycle task being aborted by engine teardown during thread unload or app exit.
Related errors
- Compaction lifecycle task ended before acknowledgement
- child-local search
- Failed to steer turn
- Failed to sync thread session
- Failed to trigger compaction: engine operation channel…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/7ff901d7741b77ec.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_threads.rs:10328
policy.permission,
configured_sandbox_mode,
);
engine.send_reserved_op(permit, op);
if let Some(settlement) = initial_routed_usage_settlement.as_mut() {
settlement.disarm();
}
touch_lru(&mut active.lru, thread_id);
self.spawn_claimed_turn_monitor(
turn.clone(),
Some(user_item),
engine.clone(),
ClaimedTurnKind::Message,
)
};
let turn = acceptance_rx
.await
.map_err(|_| anyhow!("Turn lifecycle task ended before acknowledgement"))?
.map_err(anyhow::Error::msg)?;
Ok((turn, false))
})
.await
}
pub async fn interrupt_turn(&self, thread_id: &str, turn_id: &str) -> Result<TurnRecord> {
{
let mut active = self.active.lock().await;
let Some(active_thread) = active.engines.get_mut(thread_id) else {
bail!("Thread is not loaded");
};
let Some(active_turn) = active_thread.active_turn.as_mut() else {
bail!("No active turn on thread {thread_id}");
};
if active_turn.turn_id != turn_id {
bail!("Turn {turn_id} is not active on thread {thread_id}");
}View on GitHub (pinned to 73e0f67d83)