{"record":{"id":"7edef7522177c75c","repo":"Hmbown/CodeWhale","slug":"stderr-unavailable","errorCode":null,"errorMessage":"stderr unavailable","messagePattern":"stderr unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/tui/src/lib.rs","lineNumber":8939,"sourceCode":"\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    });\n\n    if let Some(status) = child.wait_timeout(timeout)? {\n        let stdout = stdout_thread.join().unwrap_or_default();\n        let stderr = stderr_thread.join().unwrap_or_default();","sourceCodeStart":8921,"sourceCodeEnd":8957,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/lib.rs#L8921-L8957","documentation":"Defensive invariant on the stderr pipe, symmetric to the stdout one: .stderr(Stdio::piped()) is set before spawn, so child.stderr.take() must return Some. It cannot fire through normal usage; it indicates a regression where the stderr pipe was not requested or the handle was already consumed.","triggerScenarios":"Not reachable via public inputs in the current source. Appears when a fork removes .stderr(Stdio::piped()), makes it conditional, or takes the stderr handle earlier (for example, to route stderr through a different reader).","commonSituations":"Debug patches that let stderr leak to the terminal, refactors that move pipe handling into a helper, double take() after restructuring.","solutions":["Confirm .stderr(Stdio::piped()) is set on the Command before spawn","Ensure the stderr handle is taken exactly once, in the same place as the stdout take","Restore the unmodified spawn block if the file was patched for debugging"],"exampleFix":"// before (broken fork): stderr inherited\nlet mut cmd = Command::new(exec_env.program());\n// after\nlet mut cmd = Command::new(exec_env.program());\ncmd.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.stderr.take() {\n    Some(handle) => handle,\n    None => return Err(anyhow::anyhow!(\n        \"internal error: stderr pipe missing after piped spawn (report upstream)\"\n    )),\n}","preventionTips":["Keep Stdio::piped() for both streams directly on the Command builder","Take stdout and stderr handles in one place, exactly once each","Cover the take-both-handles path with a spawn integration test"],"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"}