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
- Inspect the embedded io::Error in the message for the root cause (e.g. EIO, EBADF) and address that OS-level condition
- Retry the command — transient pipe errors often disappear on rerun
- Reduce concurrent commands to relieve fd/memory pressure
- 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
- Raise ulimit -n in environments running many concurrent piped commands
- Avoid extreme process concurrency that stresses the kernel pipe buffers
- Retry transient io errors once before surfacing
- Log the embedded io::Error kind for diagnosis
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
- a process it started kept its output open
- command output pipes did not close within
- get failed for
- bootstrap from repository failed with
- brew-cask: : terminate_process match must be name or full
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)