jdx/mise · error

stderr must be piped

Error message

stderr must be piped

What it means

The stderr counterpart of the stdout invariant in `read_bounded`: the function captures and bounds both output streams, so the child must have been spawned with `stderr(Stdio::piped())`. If `child.stderr` is `None` at `.take()`, this `.expect("stderr must be piped")` panics, signaling a spawn-configuration bug rather than a runtime condition users can cause.

Source

Thrown at src/cmd.rs:1562

                    Ok(())
                });
            }
        }
        let mut cp = self
            .spawn_async_with_etxtbsy_retry()
            .await
            .wrap_err_with(|| format!("failed to execute command: {self}"))?;
        let id = cp.id().unwrap_or_default();
        let _running_pid = RunningPidGuard::new(cp.id());
        if let Some(text) = self.stdin.take()
            && let Some(mut stdin) = cp.stdin.take()
        {
            tokio::spawn(async move {
                let _ = stdin.write_all(text.as_bytes()).await;
            });
        }
        let stdout = cp.stdout.take().expect("stdout must be piped");
        let stderr = cp.stderr.take().expect("stderr must be piped");
        let stdout_task = tokio::spawn(read_capped(stdout, max_output_bytes));
        let stderr_task = tokio::spawn(read_capped(stderr, max_output_bytes));
        let status = match self.timeout {
            Some(timeout) => match tokio::time::timeout(timeout, cp.wait()).await {
                Ok(status) => status?,
                Err(_) => {
                    #[cfg(unix)]
                    signal_process_tree(id, nix::sys::signal::Signal::SIGKILL);
                    #[cfg(windows)]
                    kill_process_tree(id);
                    let _ = cp.wait().await;
                    bail!("timed out after {timeout:?}");
                }
            },
            None => cp.wait().await?,
        };
        let (stdout, stdout_len) = stdout_task.await??;
        let (stderr, stderr_len) = stderr_task.await??;

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Add `.stderr(Stdio::piped())` to the command construction before spawn
  2. Keep stdio configuration in one place for commands intended for `read_bounded`/`read_isolated`
  3. Report with `MISE_DEBUG=1` if reproducible on a stock build
  4. Cover the caller with a test that asserts both streams are captured

Example fix

// before
cmd.stderr(Stdio::inherit());
// after
cmd.stderr(Stdio::piped());
Defensive patterns

Strategy: validation

Validate before calling

// Before handing a command to output-capturing helpers:
assert_eq!(cmd.get_stderr(), Stdio::piped(), "read_bounded requires piped stderr");

Prevention

When it happens

Trigger: Command spawned with `stderr(Stdio::inherit())`, `Stdio::null()`, or unset; a code path merging stderr into stdout via `Stdio::piped()` on stdout only; refactors that configure stdio in two places and update only one.

Common situations: New callers reuse a command builder configured for passthrough execution; contributor adds a `2>&1`-style merge and forgets the separate stderr pipe expected by `read_bounded`.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/45a310aad550a0e2. Report an issue: GitHub.