jdx/mise · error
stdout must be piped
Error message
stdout must be piped
What it means
`read_bounded` captures a child command's output, which requires stdout/stderr to have been configured with `Stdio::piped()` at spawn time. This `.expect("stdout must be piped")` panics if `child.stdout` is `None`, meaning the spawn configuration and the reader drifted apart. It enforces the contract that any command built for `read_bounded` pipes both streams.
Source
Thrown at src/cmd.rs:1561
);
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??;View on GitHub (pinned to afd2eddd3a)
Solutions
- Ensure the command sets `.stdout(Stdio::piped())` before spawning (and `.stderr(Stdio::piped())` for the sibling call)
- Trace which caller constructed the command and add piping there, not in `read_bounded`
- Report with `MISE_DEBUG=1` if this occurs on an unmodified mise build
- Add an assertion at spawn time so misconfigured commands fail fast with a clear message
Example fix
// before let cmd = CmdWrapper::new(program); // inherits stdio // after let mut cmd = CmdWrapper::new(program); cmd.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped());
Defensive patterns
Strategy: validation
Validate before calling
// Before handing a command to output-capturing helpers: assert_eq!(cmd.get_stdout(), Stdio::piped(), "read_bounded requires piped stdout");
Prevention
- Configure piped stdio at command construction, once
- Never reuse interactive command builders for capture paths
- Add spawn-time assertions when introducing new execute helpers
When it happens
Trigger: A `CmdWrapper`/command built with `stdout(Stdio::inherit())`, `null()`, or default (inherit) stdio and then passed to `read_bounded`; a refactor changing spawn configuration; a helper that conditionally overrides stdout.
Common situations: Contributors add a new caller of `read_bounded` that reuses a command configured for interactive use; a global setting or wrapper mutates stdio before spawn.
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
- stderr must be piped
- piped stdout
- command wait must complete
- GitHub API URL should be valid
- GitHub API URL should support path segments
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/a56059c1cb829eb7.
Report an issue: GitHub.