jdx/mise · error
command wait must complete
Error message
command wait must complete
What it means
`execute_hashes_async_with_drain_timeout` spawns a child command and waits on it, keeping the `status` in an `Option` consumed by the timeout/drain logic. Once the wait future resolves — regardless of branch — a status must exist; this `.expect()` panics if the code reached the unwrap point without the wait ever completing. It is a control-flow invariant guard on the child-process wait path.
Solutions
- Report the panic with `MISE_DEBUG=1` and the failing command to the mise repo
- Restructure so the child's status is bound directly from the awaited `wait()`/`wait_with_output()` result instead of an `Option` filled in multiple branches
- Verify the timeout branch still awaits/consumes the child before falling through
- Run the relevant unit tests in src/cmd.rs to exercise all timeout and non-timeout branches
Example fix
// before
let status = status.expect("command wait must complete");
// after
let Some(status) = status else {
bail!("internal error: command wait did not complete");
}; Defensive patterns
Strategy: validation
Validate before calling
// Callers of cmd execution helpers: prefer APIs returning Result // and avoid wrapping child waits in Option-filled branches let status = cmd.status().await?; // propagate instead of unwrapping later
Prevention
- Bind child status directly from the awaited wait call
- Keep timeout/drain logic in one function per command type
- Treat occurrences as mise bugs; report with a debug trace
When it happens
Trigger: A logic bug where every branch that reads `status` is not actually preceded by awaiting the child's wait future; modifications to the timeout/drain structure that skip the awaited wait on some path; cancellation of the wait future without aborting the surrounding logic.
Common situations: Contributors refactoring the drain-timeout machinery in src/cmd.rs; adding a new early-return around the child wait that leaves `status` unset while still falling through to the status check.
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
- piped stdout
- stderr must be piped
- stdout must be piped
- artifact directory was initialized
- executor must be initialized before displaying cache stats
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/d800abd206095686.
Report an issue: GitHub.
Appendix: source
Thrown at src/cmd.rs:1457
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 afd2eddd3a)