Hmbown/CodeWhale · error

thread message for cannot be executed here…

Error message

thread message for {thread_id} cannot be executed here: Runtime::handle_thread does not run turns. Send it through the app-server runtime bridge (POST /v1/threads/{id}/turns).

What it means

Runtime::handle_thread in crates/core is bookkeeping-only: it owns thread metadata and persistence, never turn execution. Receiving ThreadRequest::Message on this runtime means a caller tried to run a model turn through the wrong engine; instead of faking an 'accepted' response it returns this error directing the caller to the app-server runtime bridge endpoint POST /v1/threads/{id}/turns.

Solutions

  1. Route the message through the app-server runtime bridge: POST to /v1/threads/{thread_id}/turns instead of invoking Runtime::handle_thread with ThreadRequest::Message.
  2. Update your client integration to use the app-server's thread-turn endpoint rather than the core bookkeeping API.
  3. If you only need thread bookkeeping (metadata/persistence), keep using handle_thread but do not send Message variants through it.
  4. If you believe you are already going through the bridge, verify which runtime handle your client is bound to; you are likely holding the core runtime reference directly.

Example fix

// before
core_runtime.handle_thread(ThreadRequest::Message { thread_id, message })?;
// after
app_server.post(&format!("/v1/threads/{thread_id}/turns"), &turn_payload)?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Before sending, ensure the request targets the app-server bridge, not core runtime
if let ThreadRequest::Message { thread_id, .. } = req {
    let url = format!("{app_server_base}/v1/threads/{thread_id}/turns");
    return http.post(&url).json(&turn_payload).send();
}

Type guard

fn is_bookkeeping_only(req: &ThreadRequest) -> bool {
    matches!(req, ThreadRequest::Message { .. }) // must not go to handle_thread
}

Try / catch

match runtime.handle_thread(req) {
    Err(e) if e.to_string().contains("does not run turns") => {
        // reroute through the app-server runtime bridge
        bridge_post_turns(req)?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: Sending a ThreadRequest::Message (a turn/message execution request for the given thread_id) directly into the core Runtime's handle_thread instead of routing it through the app-server HTTP runtime bridge (POST /v1/threads/{id}/turns).

Common situations: A client or plugin called the core runtime API directly instead of the app-server; an internal refactor moved message routing off the bridge; tooling replayed a message against the persistence runtime during recovery or tests.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at crates/core/src/lib.rs:1313

                    threads: Vec::new(),
                    goal: None,
                    model: None,
                    model_provider: None,
                    cwd: None,
                    approval_policy: None,
                    sandbox: None,
                    events: Vec::new(),
                    data: json!({}),
                })
            }
            // A thread message is a *turn*, and this type is not the turn
            // engine — it owns thread bookkeeping and persistence only. The
            // app-server routes messages through its runtime bridge
            // (`POST /v1/threads/{id}/turns` on the runtime API) and never
            // reaches this arm. Returning an error rather than a canned
            // "accepted" keeps any other caller from mistaking bookkeeping
            // for execution.
            ThreadRequest::Message { thread_id, .. } => Err(anyhow!(
                "thread message for {thread_id} cannot be executed here: \
                 Runtime::handle_thread does not run turns. Send it through the \
                 app-server runtime bridge (POST /v1/threads/{{id}}/turns)."
            )),
        }
    }

    /// Evaluates execution policy and dispatches a tool call.
    pub async fn invoke_tool(
        &self,
        call: ToolCall,
        approval_mode: AskForApproval,
        cwd: &Path,
    ) -> Result<Value> {
        let fallback_cwd = cwd.display().to_string();
        let (command, policy_cwd, execution_kind) = call.execution_subject(&fallback_cwd);
        let policy_tool = match &call.payload {
            ToolPayload::LocalShell { .. } => "exec_shell",

View on GitHub (pinned to 73e0f67d83)