{"record":{"id":"35633e40178342a3","repo":"Hmbown/CodeWhale","slug":"stdout-unavailable","errorCode":null,"errorMessage":"stdout unavailable","messagePattern":"stdout unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/tui/src/lib.rs","lineNumber":8935,"sourceCode":"    let spec =\n        CommandSpec::program(program, args.to_vec(), cwd.clone(), timeout).with_policy(policy);\n    let manager = SandboxManager::new();\n    let exec_env = manager.prepare(&spec);\n\n    let mut cmd = Command::new(exec_env.program());\n    cmd.args(exec_env.args())\n        .current_dir(&exec_env.cwd)\n        .stdout(Stdio::piped())\n        .stderr(Stdio::piped());\n    child_env::apply_to_command(&mut cmd, child_env::string_map_env(&exec_env.env));\n\n    let mut child = cmd\n        .spawn()\n        .map_err(|e| anyhow::anyhow!(\"Failed to run command: {e}\"))?;\n    let stdout_handle = child\n        .stdout\n        .take()\n        .ok_or_else(|| anyhow::anyhow!(\"stdout unavailable\"))?;\n    let stderr_handle = child\n        .stderr\n        .take()\n        .ok_or_else(|| anyhow::anyhow!(\"stderr unavailable\"))?;\n\n    let timeout = exec_env.timeout;\n    let stdout_thread = std::thread::spawn(move || {\n        let mut reader = stdout_handle;\n        let mut buf = Vec::new();\n        let _ = reader.read_to_end(&mut buf);\n        buf\n    });\n    let stderr_thread = std::thread::spawn(move || {\n        let mut reader = stderr_handle;\n        let mut buf = Vec::new();\n        let _ = reader.read_to_end(&mut buf);\n        buf\n    });","sourceCodeStart":8917,"sourceCodeEnd":8953,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/lib.rs#L8917-L8953","documentation":"Defensive invariant on the stdout pipe: the Command is configured with .stdout(Stdio::piped()) before spawn, so child.stdout.take() immediately after must yield Some. It cannot fire through normal usage of the shipped code; it only appears if a fork or refactor removes the piped stdout or takes the handle twice.","triggerScenarios":"Not reachable via public inputs in the current source. Appears in modified code that drops .stdout(Stdio::piped()) from the builder, conditionally sets it, or calls take() on stdout more than once before this point.","commonSituations":"Local patches that inherit stdout for debugging, refactors moving pipe setup behind a flag, or duplicated take() calls after restructuring spawn handling.","solutions":["Confirm .stdout(Stdio::piped()) is set on the same Command builder right before spawn","Ensure the stdout handle is taken exactly once and not earlier in the flow","If the file was patched, restore the original spawn block from upstream"],"exampleFix":"// before (broken fork): stdout not piped\nlet mut cmd = Command::new(exec_env.program());\ncmd.args(exec_env.args());\n// after\nlet mut cmd = Command::new(exec_env.program());\ncmd.args(exec_env.args()).stdout(Stdio::piped()).stderr(Stdio::piped());","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Treat as an internal invariant breach: report and abort, do not retry\nmatch child.stdout.take() {\n    Some(handle) => handle,\n    None => return Err(anyhow::anyhow!(\n        \"internal error: stdout pipe missing after piped spawn (report upstream)\"\n    )),\n}","preventionTips":["Keep Stdio::piped() for both streams directly on the Command builder","Take each pipe handle exactly once, immediately after spawn","Add a unit test asserting take() returns Some after a piped spawn"],"tags":["process","invariant","pipes","internal"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}