tinyhumansai/openhuman · error · anyhow::Error

[claude-code][driver] turn timed out after {:?}

Error message

[claude-code][driver] turn timed out after {:?}

What it means

The entire claude-code turn — spawn, stream, and exit — is wrapped in a hard 300-second (`TURN_TIMEOUT`, driver.rs:19) tokio timeout. When the child does not finish within 5 minutes, the driver logs a timeout, explicitly kills the child (kill_on_drop is the backstop; the explicit kill lets stderr be collected), and bails with this message.

Source

Thrown at src/openhuman/inference/provider/claude_code/driver.rs:476

        let status = child
            .wait()
            .await
            .map_err(|e| anyhow::anyhow!("wait child: {e}"))?;
        Ok::<_, anyhow::Error>(status)
    })
    .await;

    let status = match timed {
        Ok(inner) => inner?,
        Err(_elapsed) => {
            log::error!(
                "[claude-code][driver] turn timeout ({TURN_TIMEOUT:?}) exceeded; killing child"
            );
            // kill_on_drop handles cleanup, but explicit kill gives us
            // a chance to collect stderr.
            let _ = child.kill().await;
            anyhow::bail!(
                "[claude-code][driver] turn timed out after {:?}",
                TURN_TIMEOUT
            );
        }
    };

    let stderr_text = stderr_task.await.unwrap_or_default();

    if !status.success() {
        anyhow::bail!(
            "[claude-code][driver] exit {:?} stderr={}",
            status.code(),
            stderr_text.trim()
        );
    }
    if let Some(err) = mapper.error.clone() {
        anyhow::bail!("[claude-code][driver] {}", err);
    }

View on GitHub (pinned to 7491200858)

Solutions

  1. Retry once — transient stalls (proxy hiccup, API slowdown) commonly clear.
  2. If turns legitimately exceed 5 minutes in your workload, raise `TURN_TIMEOUT` in driver.rs:19 (it is a single const) and rebuild.
  3. Check `claude mcp list` for MCP servers whose tools hang; fix or remove them.
  4. Ensure the project dir is pre-trusted (run `claude` once interactively in it) so the CLI never blocks on a trust prompt.

Example fix

// before
const TURN_TIMEOUT: Duration = Duration::from_secs(300);

// after — allow long agentic turns
const TURN_TIMEOUT: Duration = Duration::from_secs(900);
Defensive patterns

Strategy: retry

Try / catch

let attempt = tokio::time::timeout(Duration::from_secs(330), provider.chat(req)).await;
match attempt {
    Ok(Err(e)) if e.to_string().contains("turn timed out") => {
        // one retry; a second consecutive timeout indicates a hung MCP tool or stall
        provider.chat(req).await
    }
    other => other.unwrap_or_else(|_| Err(anyhow::anyhow!("outer watchdog fired"))),
}

Prevention

When it happens

Trigger: Any claude-code turn whose CLI process runs longer than 300s: a large refactor with many tool iterations, a hung MCP tool call, a CLI permission prompt blocking in non-interactive mode, or a stalled network uplink. Fires from the `Err(_elapsed)` arm of `tokio::time::timeout(TURN_TIMEOUT, ...)` at driver.rs:434.

Common situations: Big agentic coding tasks that legitimately exceed 5 minutes; an MCP server tool that never returns; the CLI waiting for interactive trust approval on a new project directory; saturated corporate proxy blocking streaming responses.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/dfcdb837a20e84ec. Report an issue: GitHub.