jdx/mise · error

failed to read command

Error message

failed to read command {stream}: {err}

What it means

This error is thrown when an asynchronous reader task fails to read from one of the command's piped output streams (stdout or stderr) while hashing output in execute_hashes_async_with_drain_timeout. The underlying io::Error is embedded in the message along with the stream name, so the failure is a low-level pipe/OS read error rather than a command failure. The process tree is killed and the error propagated instead of returning hashes.

Solutions

  1. Inspect the embedded io::Error in the message for the root cause (e.g. EIO, EBADF) and address that OS-level condition
  2. Retry the command — transient pipe errors often disappear on rerun
  3. Reduce concurrent commands to relieve fd/memory pressure
  4. Check for resource limits (ulimit -n) and raise them if file descriptors are exhausted

Example fix

// before
// Err: failed to read command stderr: Os { code: 9, kind: Uncategorized, message: "Bad file descriptor" }
// after (raise fd limit, then retry)
ulimit -n 4096
mise run my-task
Defensive patterns

Strategy: retry

Validate before calling

// preflight: ensure fd limits are comfortable
let nofile = std::fs::read_to_string("/proc/self/limits")?;
if !nofile.contains("open files") { /* warn */ }

Try / catch

match cmd.execute_hashes_async(MAX).await {
    Ok(h) => Ok(h),
    Err(e) if e.to_string().starts_with("failed to read command") => {
        tokio::time::sleep(Duration::from_millis(500)).await;
        cmd.execute_hashes_async(MAX).await // one retry
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: The spawned reader task's stdout.read()/stderr.read() loop returns Err — e.g. the pipe descriptor becomes invalid, the OS returns EIO, or the process/pipe is torn down unexpectedly mid-read.

Common situations: System resource exhaustion (fd/memory pressure), a child that closes its end of the pipe in an unexpected way, or platform-specific pipe errors when many concurrent tasks run with piped output.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/2a6af38b4c2eaa81. Report an issue: GitHub.

Appendix: source

Thrown at src/cmd.rs:1405

        let mut output_bytes = 0usize;
        let mut consume = |output: HashedProcessOutput| -> Result<()> {
            match output {
                HashedProcessOutput::Stdout(bytes) => {
                    output_bytes = output_bytes.saturating_add(bytes.len());
                    if output_bytes > max_output_bytes {
                        bail!("command output exceeded {max_output_bytes} bytes");
                    }
                    stdout_hasher.update(&bytes);
                }
                HashedProcessOutput::Stderr(bytes) => {
                    output_bytes = output_bytes.saturating_add(bytes.len());
                    if output_bytes > max_output_bytes {
                        bail!("command output exceeded {max_output_bytes} bytes");
                    }
                    stderr_hasher.update(&bytes);
                }
                HashedProcessOutput::ReadError(stream, err) => {
                    bail!("failed to read command {stream}: {err}");
                }
            }
            Ok(())
        };
        let mut status = None;
        let mut wait = Box::pin(cp.wait());
        loop {
            tokio::select! {
                result = &mut wait, if status.is_none() => {
                    status = Some(result?);
                    break;
                }
                output = rx.recv() => {
                    let Some(output) = output else {
                        status = Some(wait.await?);
                        break;
                    };
                    if let Err(err) = consume(output) {

View on GitHub (pinned to 533346cc37)