jdx/mise · error
command wait must complete
Error message
command wait must complete
What it means
In the hashed-output process runner (src/cmd.rs), a tokio::select loop drives both `cp.wait()` and consumption of stdout/stderr chunks; every exit path of the loop assigns `status = Some(...)` (either the wait branch completes or the channel closes and `wait.await` finishes, lines 1314-1337). The trailing `status.expect("command wait must complete")` asserts that invariant. It cannot be None in the current code — the loop only breaks after setting it — so hitting it means the loop was refactored to exit by another means.
Source
Thrown at src/cmd.rs:1371
signal_process_tree(id, nix::sys::signal::Signal::SIGKILL);
#[cfg(windows)]
kill_process_tree(id);
bail!("command output pipes did not close within {pipe_drain_timeout:?}");
}
};
if let Err(err) = consume(output) {
#[cfg(unix)]
signal_process_tree(id, nix::sys::signal::Signal::SIGKILL);
#[cfg(windows)]
kill_process_tree(id);
return Err(err);
}
}
if let Some(guard) = &timeout_guard {
guard.cancel();
}
let status = status.expect("command wait must complete");
if !status.success() {
if let Some(timeout) = timeout_guard.as_ref().and_then(|guard| guard.timed_out()) {
bail!("timed out after {timeout:?}");
}
bail!("exited with non-zero status: {status}");
}
Ok((
stdout_hasher.finalize().to_hex().to_string(),
stderr_hasher.finalize().to_hex().to_string(),
))
}
/// Run the command and return stdout, even when raw mode is enabled.
pub(crate) async fn read(mut self) -> Result<String> {
let _read_lock = RAW_LOCK.read().await;
debug!("$ {self}");
self.cmd.kill_on_drop(true);
#[cfg(unix)]View on GitHub (pinned to 6f52dcdf99)
Solutions
- If hit as a user: update mise — internal regression in the process runner; include the panic backtrace in a report
- As a contributor: ensure every `break` in the select loop is preceded by `status = Some(...)` (or await the wait future before breaking)
- Consider restructuring so the compiler enforces it: compute status via a dedicated function returning Result<ExitStatus>
- Report at https://github.com/jdx/mise/issues
Example fix
// before: new branch breaks without recording status
loop {
tokio::select! {
r = &mut wait, if status.is_none() => { status = Some(r?); break; }
output = rx.recv() => {
let Some(o) = output else { status = Some(wait.await?); break; };
if too_big(o) { break; } // BUG: status stays None -> panic
}
}
}
// after: every break completes the wait first
if too_big(o) { status = Some(wait.await?); break; } Defensive patterns
Strategy: validation
Prevention
- Treat this panic as an internal regression — collect the backtrace with MISE_DEBUG=1 and report upstream
- Contributors: every break in the select loop must record the wait status first
- Pin released builds; timeouts and output limits use explicit error paths and are safe
When it happens
Trigger: Only an internal change to the select loop that adds a break without assigning status (e.g. a new output-limit or cancellation branch). Timeouts, output-byte caps, and pipe-drain failures all take explicit bail!/return paths that never reach this expect; the OS-level wait result is always stored before the loop exits.
Common situations: Contributors editing the process-pumping logic in src/cmd.rs (e.g. adding a new early-exit condition); not triggerable by command output, environment, or the child process itself in shipped builds.
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
- enabled accounts were prepared
- configured notifications came from managed files
- configured notifications prepared services
- system files were preflighted when not skipped
- BootstrapPart values have clap names
AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22).
Data as JSON: /api/errors/d800abd206095686.
Report an issue: GitHub.