{"record":{"id":"1b577b441cfa186c","repo":"tinyhumansai/openhuman","slug":"claude-agent-sdk-subprocess-timed-out-while-read","errorCode":null,"errorMessage":"[claude_agent_sdk] subprocess timed out while reading output","messagePattern":"\\[claude_agent_sdk\\] subprocess timed out while reading output","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/openhuman/inference/provider/claude_agent_sdk/subprocess.rs","lineNumber":214,"sourceCode":"                    }\n                    Err(e) => {\n                        tracing::warn!(\n                            error = %e,\n                            line_len = line.len(),\n                            \"[claude_agent_sdk] failed to parse ndjson line\"\n                        );\n                    }\n                }\n            }\n            anyhow::Ok(())\n        })\n        .await;\n\n        match read_result {\n            Ok(inner) => inner?,\n            Err(_) => {\n                let _ = child.kill().await;\n                anyhow::bail!(\"[claude_agent_sdk] subprocess timed out while reading output\");\n            }\n        }\n\n        let status = timeout(Duration::from_secs(30), child.wait())\n            .await\n            .map_err(|_| {\n                anyhow::anyhow!(\"[claude_agent_sdk] subprocess timed out while waiting for exit\")\n            })??;\n        let stderr_output = stderr_task.await.unwrap_or_default();\n        tracing::debug!(\"[claude_agent_sdk] subprocess exited status={}\", status);\n\n        if let Some(err) = error_message {\n            anyhow::bail!(\"[claude_agent_sdk] error from claude CLI: {err}\");\n        }\n\n        // Use the final result message if present; otherwise join streaming text parts.\n        let output = result_text\n            .filter(|s| !s.is_empty())","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/inference/provider/claude_agent_sdk/subprocess.rs#L196-L232","documentation":"The claude_agent_sdk provider spawns the `claude` CLI as a child process and reads NDJSON messages from its stdout under a 120-second tokio timeout (subprocess.rs:158). This error fires when that outer timeout elapses before stdout reaches EOF — i.e. the CLI produced no complete output stream within 2 minutes. The child is explicitly killed before bailing, so no orphan process remains.","triggerScenarios":"A `claude` SDK-mode request where the CLI hangs: a very long-running agentic turn exceeding 120s, a CLI waiting on a stuck MCP server or permission prompt, a deadlocked stdin pipe, or a network stall between the CLI and Anthropic APIs. Triggered by the `Err(_elapsed)` arm of `timeout(Duration::from_secs(120), ...)` around the stdout line-reading loop.","commonSituations":"Long agent turns (tool-heavy sessions routinely exceed 2 minutes), corporate proxies that stall the CLI's HTTPS connection, a claude CLI version that blocks on interactive trust prompts when run non-interactively, or an MCP server configured in the project that never responds.","solutions":["Retry the request — transient CLI/network stalls are the most common cause and a fresh turn often completes.","Check the core debug log for `[claude_agent_sdk]` lines: if the last NDJSON line arrived recently, the turn was genuinely long; consider raising the 120s timeout in subprocess.rs:158 to fit your workload.","Run `claude --version` and update the CLI; older builds hang on permission/trust prompts in non-interactive mode.","Audit configured MCP servers (`claude mcp list`) for a server that never completes its handshake and stalls the turn."],"exampleFix":"// before\nlet read_result = timeout(Duration::from_secs(120), async { /* read stdout */ }).await;\n\n// after — size the deadline to the workload (long agentic turns)\nlet read_result = timeout(Duration::from_secs(600), async { /* read stdout */ }).await;","handlingStrategy":"retry","validationCode":"// Rust — pre-flight the CLI before routing long turns to it\nasync fn claude_sdk_ready() -> bool {\n    tokio::process::Command::new(\"claude\")\n        .arg(\"--version\")\n        .output()\n        .await\n        .map(|o| o.status.success())\n        .unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match provider.chat(req).await {\n    Err(e) if e.to_string().contains(\"timed out while reading output\") => {\n        // transient stall: retry once with backoff, then surface to user\n        tokio::time::sleep(Duration::from_secs(3)).await;\n        provider.chat(req).await\n    }\n    other => other,\n}","preventionTips":["Keep the claude CLI updated so it never blocks on interactive prompts in non-interactive mode.","Audit MCP servers used by the CLI for hanging tools before routing automated turns.","Size expectations: turns regularly exceeding 120s need a raised read timeout in subprocess.rs."],"tags":["claude-cli","subprocess","timeout","inference"],"backgroundTag":"subprocess-timeout","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}