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

  1. Ensure the command has both `stdout(Stdio::piped())` and `stderr(Stdio::piped())` set before calling `read_isolated`
  2. Remove any `stderr(...)` override applied between command construction and the bounded spawn
  3. If output should be discarded, capture it via the pipe and drop it instead of using `Stdio::null()`
  4. 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

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


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