{"record":{"id":"fa786dc6ae3530f1","repo":"nikivdev/code","slug":"failed-to-capture-stdout","errorCode":null,"errorMessage":"failed to capture stdout","messagePattern":"failed to capture stdout","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/storage.rs","lineNumber":394,"sourceCode":"\nfn jazz_tools_package_spec() -> String {\n    resolve_jazz_tools_package_spec(std::env::var(JAZZ_TOOLS_NPX_SPEC_ENV).ok().as_deref())\n}\n\nfn resolve_jazz_tools_package_spec(raw: Option<&str>) -> String {\n    raw.map(str::trim)\n        .filter(|value| !value.is_empty())\n        .unwrap_or(DEFAULT_JAZZ_TOOLS_NPX_SPEC)\n        .to_string()\n}\n\nfn run_command_with_output(mut cmd: Command) -> Result<Output> {\n    let mut child = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;\n\n    let mut stdout = child\n        .stdout\n        .take()\n        .ok_or_else(|| anyhow::anyhow!(\"failed to capture stdout\"))?;\n    let mut stderr = child\n        .stderr\n        .take()\n        .ok_or_else(|| anyhow::anyhow!(\"failed to capture stderr\"))?;\n\n    let stdout_handle = thread::spawn(move || {\n        let mut buf = Vec::new();\n        let _ = stdout.read_to_end(&mut buf);\n        buf\n    });\n    let stderr_handle = thread::spawn(move || {\n        let mut buf = Vec::new();\n        let _ = stderr.read_to_end(&mut buf);\n        buf\n    });\n\n    let start = Instant::now();\n    let mut next_log = Duration::from_secs(10);","sourceCodeStart":376,"sourceCodeEnd":412,"githubUrl":"https://github.com/nikivdev/code/blob/a747e741ae92c09071d0ae946ab48488adcff1ce/src/storage.rs#L376-L412","documentation":"run_command_with_output spawns a child process with stdout and stderr piped, then takes() both handles. If the child's stdout handle is somehow absent, take() returns None and the function throws 'failed to capture stdout'. This is a defensive guard: after Stdio::piped() the handle should always exist.","triggerScenarios":"Calling run_command_with_output (via create_jazz_app_credentials) after the Command was mutated to override stdout with something that removes the pipe (e.g. Stdio::inherit or Stdio::null set after piped), making child.stdout None.","commonSituations":"Practically unreachable with the current call sites; could occur if a caller pre-configures the Command with a different stdio before passing it in and the code is changed to drop the piped() call.","solutions":["Ensure the Command passed in does not set stdout/stderr before calling run_command_with_output","Keep cmd.stdout(Stdio::piped()).stderr(Stdio::piped()) as the final stdio configuration before spawn()","If hit, inspect the Command construction at the call site for conflicting stdio settings"],"exampleFix":"// before\nlet mut child = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;\n// after (caller side)\nlet mut cmd = Command::new(\"jazz-tools\");\ncmd.args(&[\"apps\", \"create\"]); // do NOT set stdout/stderr here\nlet output = run_command_with_output(cmd)?;","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match run_command_with_output(cmd) {\n    Ok(out) => out,\n    Err(e) if e.to_string().contains(\"failed to capture stdout\") => {\n        // stdio misconfiguration: rebuild Command without pre-set stdout\n        eprintln!(\"do not override stdout before calling run_command_with_output\");\n        return Err(e);\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Never set stdout/stderr on the Command before handing it to run_command_with_output","Keep Stdio::piped() calls immediately before spawn()","Add a unit test that a fresh Command yields Some handles after spawn"],"tags":["subprocess","stdio","defensive-code"],"backgroundTag":"missing-stdio-pipe","analyzedSha":"a747e741ae92c09071d0ae946ab48488adcff1ce","analyzedAt":"2026-09-01T22:43:55.719Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}