nikivdev/code · warning

failed to capture stdout

Error message

failed to capture stdout

What it means

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.

Source

Thrown at src/storage.rs:394

fn jazz_tools_package_spec() -> String {
    resolve_jazz_tools_package_spec(std::env::var(JAZZ_TOOLS_NPX_SPEC_ENV).ok().as_deref())
}

fn resolve_jazz_tools_package_spec(raw: Option<&str>) -> String {
    raw.map(str::trim)
        .filter(|value| !value.is_empty())
        .unwrap_or(DEFAULT_JAZZ_TOOLS_NPX_SPEC)
        .to_string()
}

fn run_command_with_output(mut cmd: Command) -> Result<Output> {
    let mut child = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;

    let mut stdout = child
        .stdout
        .take()
        .ok_or_else(|| anyhow::anyhow!("failed to capture stdout"))?;
    let mut stderr = child
        .stderr
        .take()
        .ok_or_else(|| anyhow::anyhow!("failed to capture stderr"))?;

    let stdout_handle = thread::spawn(move || {
        let mut buf = Vec::new();
        let _ = stdout.read_to_end(&mut buf);
        buf
    });
    let stderr_handle = thread::spawn(move || {
        let mut buf = Vec::new();
        let _ = stderr.read_to_end(&mut buf);
        buf
    });

    let start = Instant::now();
    let mut next_log = Duration::from_secs(10);

View on GitHub (pinned to a747e741ae)

Solutions

  1. Ensure the Command passed in does not set stdout/stderr before calling run_command_with_output
  2. Keep cmd.stdout(Stdio::piped()).stderr(Stdio::piped()) as the final stdio configuration before spawn()
  3. If hit, inspect the Command construction at the call site for conflicting stdio settings

Example fix

// before
let mut child = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;
// after (caller side)
let mut cmd = Command::new("jazz-tools");
cmd.args(&["apps", "create"]); // do NOT set stdout/stderr here
let output = run_command_with_output(cmd)?;
Defensive patterns

Strategy: try-catch

Try / catch

match run_command_with_output(cmd) {
    Ok(out) => out,
    Err(e) if e.to_string().contains("failed to capture stdout") => {
        // stdio misconfiguration: rebuild Command without pre-set stdout
        eprintln!("do not override stdout before calling run_command_with_output");
        return Err(e);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01). Data as JSON: /api/errors/fa786dc6ae3530f1. Report an issue: GitHub.