Hmbown/CodeWhale · error

no engine installed for

Error message

no engine installed for {thread_id}

What it means

Setting a turn's active state requires an engine already installed for the thread in the RuntimeThreadManager's active map. If active.engines has no entry for thread_id, the lookup fails with anyhow!("no engine installed for {thread_id}"). It is an internal sequencing error: the engine must be spawned/registered for a thread before turn operations target it.

Solutions

  1. Ensure the engine for thread_id is installed (spawned via the manager) before starting or interrupting turns
  2. Check earlier logs for a swallowed engine-installation failure for this thread
  3. Re-create/reinstall the thread's engine if it was torn down
  4. Validate the thread_id — it may come from a stale or foreign session

Example fix

// before
manager.set_active_turn(&thread_id, Some(&turn_id)).await?; // engine may not exist
// after
if manager.has_engine(&thread_id).await {
    manager.set_active_turn(&thread_id, Some(&turn_id)).await?;
} else {
    manager.install_engine(&thread_id).await?;
    manager.set_active_turn(&thread_id, Some(&turn_id)).await?;
}
Defensive patterns

Strategy: type-guard

Validate before calling

let has_engine = manager.active.lock().await.engines.contains_key(thread_id);
if !has_engine { bail!("engine not installed for {thread_id}; install it first"); }

Type guard

async fn engine_installed(manager: &RuntimeThreadManager, thread_id: &str) -> bool {
    manager.active.lock().await.engines.contains_key(thread_id)
}

Try / catch

match manager.set_active_turn(thread_id, turn).await {
    Err(e) if e.to_string().starts_with("no engine installed") => {
        manager.install_engine(thread_id).await?;
        manager.set_active_turn(thread_id, turn).await?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling the turn-state setter (active_turn update) with a thread_id that has no engine in active.engines — e.g. referencing a thread before its engine was installed, after the engine was removed/cleaned up, or after a shutdown removed engines.

Common situations: A stale turn_id/thread_id from a previous session being replayed; racing shutdown while a turn op is still queued; a bug where engine installation failed earlier and the error was swallowed.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/7f6f5497db4e5a0f. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/runtime_threads.rs:8193

                );
            }
        }
        Ok(())
    }

    /// Test seam: mark or clear an active turn on an installed test engine so
    /// route-level tests can exercise restore admission.
    #[cfg(test)]
    pub(crate) async fn set_active_turn_for_test(
        &self,
        thread_id: &str,
        turn_id: Option<&str>,
    ) -> Result<()> {
        let mut active = self.active.lock().await;
        let state = active
            .engines
            .get_mut(thread_id)
            .ok_or_else(|| anyhow!("no engine installed for {thread_id}"))?;
        state.active_turn = turn_id.map(|turn_id| ActiveTurnState {
            turn_id: turn_id.to_string(),
            goal_id: None,
            interrupt_requested: false,
            compaction_id: None,
        });
        Ok(())
    }

    /// Bind full-fidelity saved history to the exact prefix it covers. Callers
    /// hold session_checkpoint_guard across the snapshot, save and this write.
    pub(crate) async fn set_thread_session_checkpoint(
        &self,
        thread_id: &str,
        session: &crate::session_manager::SavedSession,
    ) -> Result<()> {
        let session_id = &session.metadata.id;
        let messages_sha256 = session_messages_sha256(&session.messages)?;

View on GitHub (pinned to 73e0f67d83)