tinyhumansai/openhuman · error · anyhow::Error

[claude_agent_sdk] claude subprocess exited with non-zero st

Error message

[claude_agent_sdk] claude subprocess exited with non-zero status {} and no output; stderr={}

What it means

The claude CLI child process exited with a non-zero status AND the provider collected no usable output — neither a final `Result` text nor any streamed `Text` parts. The captured stderr is included in the message. This is the last-resort diagnosis when the CLI died before saying anything useful on stdout.

Source

Thrown at src/openhuman/inference/provider/claude_agent_sdk/subprocess.rs:236

        let status = timeout(Duration::from_secs(30), child.wait())
            .await
            .map_err(|_| {
                anyhow::anyhow!("[claude_agent_sdk] subprocess timed out while waiting for exit")
            })??;
        let stderr_output = stderr_task.await.unwrap_or_default();
        tracing::debug!("[claude_agent_sdk] subprocess exited status={}", status);

        if let Some(err) = error_message {
            anyhow::bail!("[claude_agent_sdk] error from claude CLI: {err}");
        }

        // Use the final result message if present; otherwise join streaming text parts.
        let output = result_text
            .filter(|s| !s.is_empty())
            .unwrap_or_else(|| text_parts.join(""));

        if !status.success() && output.is_empty() {
            anyhow::bail!(
                "[claude_agent_sdk] claude subprocess exited with non-zero status {} and no output; stderr={}",
                status,
                stderr_output
            );
        }

        tracing::debug!(
            "[claude_agent_sdk] response collected output_len={}",
            output.len()
        );

        Ok(output)
    }
}

#[async_trait]
impl ChatModel<()> for ClaudeAgentSdkProvider {
    fn profile(&self) -> Option<&ModelProfile> {

View on GitHub (pinned to 7491200858)

Solutions

  1. Read the `stderr=` portion of the message — it usually contains the crash reason (missing module, bad flag, auth failure).
  2. Run the same CLI manually (`claude --version`, then a trivial prompt) to reproduce the crash outside the app.
  3. Reinstall/upgrade the claude CLI (`npm i -g @anthropic-ai/claude-code` or the native installer) to repair a broken auto-updated install.
  4. If stderr shows OOM/signal kill, free memory or reduce parallel agent turns.
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: does the CLI start and emit a version at all?
fn claude_cli_bootable() -> bool {
    std::process::Command::new("claude")
        .arg("--version")
        .output()
        .map(|o| o.status.success())
        .unwrap_or(false)
}

Try / catch

match provider.chat(req).await {
    Err(e) if e.to_string().contains("non-zero status") => {
        log::error!("claude CLI crashed: {e}"); // stderr is embedded — log it for triage
        Err(e)
    }
    other => other,
}

Prevention

When it happens

Trigger: The `claude` binary crashes at startup (bad flag, missing runtime like an incompatible Node version), fails auth before emitting NDJSON, or is killed by the OS (OOM, signal). Only fires when `!status.success() && output.is_empty()` — a non-zero exit WITH output is treated as success.

Common situations: CLI binary/arch mismatch after an OS update, claude CLI auto-update mid-run leaving a broken install, node runtime unavailable to the CLI wrapper, OOM killer terminating the child on memory-constrained machines, or an invalid CLI flag introduced by a version skew between the core's arg builder and the installed CLI.

Related errors


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