jdx/mise · error

piped

Error message

piped

What it means

run_with_limits spawns a child with stdin piped, then takes the StdinChildPipe with expect("piped"). Because the child was created with Stdio::piped() for stdin, this can only fail if the internal construction changed — the expect documents an invariant rather than an expected runtime failure.

Solutions

  1. Restore Stdio::piped() for stdin when spawning the child in run_with_limits
  2. Use match/expect with a descriptive message and handle None gracefully if piped stdin becomes conditional

Example fix

// before
let mut stdin = child.stdin.take().expect("piped");
// after
let Some(mut stdin) = child.stdin.take() else {
    return Err(anyhow!("child stdin was not piped"));
};
Defensive patterns

Strategy: try-catch

Try / catch

// if contributing upstream, prefer explicit handling over expect
let Some(mut stdin) = child.stdin.take() else {
    return Err(anyhow!("stdin not piped"));
};

Prevention

When it happens

Trigger: Calling describe_command's run/run_with_limits when the child process creation code no longer configures stdin as piped — i.e. only after a code change breaks the invariant; not triggerable from user input.

Common situations: Developers modifying describe_command.rs and changing the Command's stdio configuration; reviewers seeing this panic in CI after such a change.

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@533346cc37 (2026-09-17). Data as JSON: /api/errors/6ca0e43045613973. Report an issue: GitHub.

Appendix: source

Thrown at src/system/history/describe_command.rs:146

        use std::os::unix::process::CommandExt;
        // its own process group: a timeout ends what the shell started too
        shell.process_group(0);
    }
    #[cfg(windows)]
    let (mut child, job) = windows_job::spawn(&mut shell)?;
    #[cfg(not(windows))]
    let mut child = shell.spawn()?;
    let active = ActiveCommand(RunningCommand {
        pid: child.id(),
        #[cfg(windows)]
        job: std::sync::Arc::new(job),
    });
    if let Ok(mut running) = RUNNING.lock() {
        *running = Some(active.0.clone());
    }
    // stdin is written and closed on its own thread: a command that answers
    // before reading everything must not block us
    let mut stdin = child.stdin.take().expect("piped");
    std::thread::spawn(move || {
        let _ = stdin.write_all(&input);
    });
    let stdout = child.stdout.take().expect("piped");
    let (sender, receiver) = std::sync::mpsc::channel();
    std::thread::spawn(move || {
        let mut out = Vec::new();
        let _ = stdout.take(DIFF_LIMIT as u64 + 1).read_to_end(&mut out);
        let _ = sender.send(out);
    });
    let started = Instant::now();
    let status = loop {
        if let Some(status) = child.try_wait()? {
            break status;
        }
        if started.elapsed() >= timeout {
            // the shell and whatever it started; the reader thread ends
            // with the last writer of the pipe, so it is not waited for

View on GitHub (pinned to 533346cc37)