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

  1. Retry the steer after confirming the thread's turn is still active
  2. Check logs for a panic or early return inside the spawn_steer_receipts task
  3. 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

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


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)