jdx/mise · error
piped stderr
Error message
piped stderr
What it means
mise panics with 'piped stderr' when a spawned child process has no stderr pipe to take. The `read_isolated` helper requires the command to have been configured with stdout and stderr both piped (via `stdin/stdout/stderr(Stdio::piped())`) before spawning, because it must capture and bound both pipes with a shared budget. If stderr was set to `inherit`, `null`, or another non-piped mode, `child.stderr` is `None` and the `.expect()` panics immediately after spawn.
Source
Thrown at src/cmd/bounded.rs:28
/// This command owns its child tree even when mise itself is nested.
pub(crate) async fn read_isolated(mut self, limit: usize) -> Result<String> {
let _read_lock = RAW_LOCK.read().await;
let timeout = self.timeout.unwrap_or(Duration::from_secs(5));
self.cmd.kill_on_drop(true);
self.cmd
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(unix)]
{
self.cmd.env(TASK_PGID_MANAGED_ENV, "1");
self.cmd.process_group(0);
}
let mut child = self.spawn_async_with_etxtbsy_retry().await?;
let _running = RunningPidGuard::new(child.id());
let tree = ChildTree::new(&mut child)?;
let stdout = child.stdout.take().expect("piped stdout");
let stderr = child.stderr.take().expect("piped stderr");
// One budget for both pipes: judging them only once both reach EOF
// would let each hold the whole limit first, so a command could
// allocate twice what was asked before anyone objected.
let budget = AtomicUsize::new(limit);
let result = tokio::time::timeout(timeout, async {
tokio::try_join!(
child.wait(),
capture(stdout, &budget, limit),
capture(stderr, &budget, limit)
)
})
.await;
let (status, stdout, _stderr) = match result {
Ok(Ok(output)) => output,
Ok(Err(err)) => {
end(&mut child, tree).await;
return Err(err.into());
}View on GitHub (pinned to afd2eddd3a)
Solutions
- Ensure the command has both `stdout(Stdio::piped())` and `stderr(Stdio::piped())` set before calling `read_isolated`
- Remove any `stderr(...)` override applied between command construction and the bounded spawn
- If output should be discarded, capture it via the pipe and drop it instead of using `Stdio::null()`
- If output must be inherited, use a different run path that does not require capturing both pipes
Example fix
// before let mut cmd = Cmd::new(program); cmd.stderr(Stdio::inherit()); let out = cmd.read_isolated(limit, timeout).await?; // after let mut cmd = Cmd::new(program); // keep both pipes so read_isolated can budget them cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); let out = cmd.read_isolated(limit, timeout).await?;
Defensive patterns
Strategy: validation
Validate before calling
debug_assert!(cmd.get_stdout().is_piped() && cmd.get_stderr().is_piped(), "read_isolated requires piped stdout+stderr");
Type guard
fn has_pipes(cmd: &Cmd) -> bool { cmd.piped_stdout() && cmd.piped_stderr() } Prevention
- Never override stderr/stdout to inherit or null on commands routed through read_isolated
- Centralize bounded command spawning in one constructor that always sets both pipes
- Add a debug assertion for piped stdio before spawn
When it happens
Trigger: Calling `Cmd::read_isolated` (or the bounded-run path in src/cmd/bounded.rs) on a `Cmd` whose stderr was overridden to `Stdio::inherit()`, `Stdio::null()`, or a file after the default piped configuration; any code path that constructs the command and then disables stderr piping before `spawn_async_with_etxtbsy_retry`.
Common situations: A contributor adds a new caller of the bounded command runner and passes `Stdio::inherit()` for stderr to forward output to the terminal, not realizing `read_isolated` needs to capture stderr; refactoring `cmd!` defaults so one of the pipes is no longer set; platform-specific spawn paths that drop the stderr pipe.
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
- configured notifications came from managed files
- configured notifications prepared services
- system files were preflighted when not skipped
- new config Arc is uniquely owned
- explicit task name was just found
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/4d893a8f354c9ba3.
Report an issue: GitHub.