Hmbown/CodeWhale · error · anyhow::Error

thread '{thread_id}' not found

Error message

thread '{thread_id}' not found

What it means

submit_user_input could not find thread_id in the active engines map. The user-input protocol only works against a thread whose engine is currently started (an active turn exists to receive the answer), so a thread that was never started, has finished, or was evicted fails here.

Source

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

            }
        };
        let ack =
            self.spawn_dynamic_tool_settlement(claim, DynamicToolTerminalOutcome::Resolved(result));
        Ok(Self::await_dynamic_tool_settlement(ack)
            .await?
            .result_accepted)
    }

    pub async fn submit_user_input(
        &self,
        thread_id: &str,
        input_id: &str,
        response: crate::tools::user_input::UserInputResponse,
    ) -> Result<bool> {
        let engine = {
            let active = self.active.lock().await;
            let Some(state) = active.engines.get(thread_id) else {
                bail!("thread '{thread_id}' not found");
            };
            state.engine.clone()
        };
        let request = match self.claim_pending_user_input(thread_id, input_id) {
            PendingUserInputClaim::Claimed(request) => request,
            PendingUserInputClaim::Missing | PendingUserInputClaim::Settling => {
                return Ok(false);
            }
            PendingUserInputClaim::Indeterminate => {
                bail!(
                    "User-input request '{input_id}' has an indeterminate terminal receipt; inspect Runtime storage before retrying"
                );
            }
        };

        // This child task deliberately outlives the HTTP future. Once a
        // request is claimed, client disconnect/cancellation cannot strand it
        // between durable acceptance and engine delivery.

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Check the thread exists and has a live engine before answering (poll thread status / pending requests first)
  2. If the engine legitimately ended, discard the stale request - the Ok(false) contract for Missing/Settling shows the runtime distinguishes this from hard errors
  3. Re-poll pending_requests_for_thread to confirm the input_id is still advertised before submitting
  4. Verify thread_id spelling and that the thread was started in this runtime session
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the thread has a live engine before answering.
let thread = manager.get_thread(thread_id).await?; // hard error if the record is gone
if !manager.has_active_engine(thread_id).await { // or your equivalent liveness check
    return Ok(()); // engine ended; drop the stale request
}
manager.submit_user_input(thread_id, input_id, response).await?;

Try / catch

// Distinguish 'engine gone' (restartable) from real errors.
match manager.submit_user_input(thread_id, input_id, response).await {
    Ok(accepted) => Ok(accepted),
    Err(e) if e.to_string().contains("thread '") && e.to_string().contains("not found") => {
        // Re-check thread state; restart it or discard the stale request.
        Ok(false)
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Calling submit_user_input after the turn/engine ended (the pending request expired with the engine), with a stale or mistyped thread_id, or before start_thread completed. See runtime_threads.rs:3274-3279.

Common situations: Client held a request id across a runtime restart and resumed answering after the engine shut down; race between turn completion and a late user response; thread archived or evicted by a workspace change between polling the request and answering.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/dff1e9722590721c. Report an issue: GitHub.