{"record":{"id":"d800abd206095686","repo":"jdx/mise","slug":"command-wait-must-complete","errorCode":null,"errorMessage":"command wait must complete","messagePattern":"command wait must complete","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/cmd.rs","lineNumber":1371,"sourceCode":"                    signal_process_tree(id, nix::sys::signal::Signal::SIGKILL);\n                    #[cfg(windows)]\n                    kill_process_tree(id);\n                    bail!(\"command output pipes did not close within {pipe_drain_timeout:?}\");\n                }\n            };\n            if let Err(err) = consume(output) {\n                #[cfg(unix)]\n                signal_process_tree(id, nix::sys::signal::Signal::SIGKILL);\n                #[cfg(windows)]\n                kill_process_tree(id);\n                return Err(err);\n            }\n        }\n\n        if let Some(guard) = &timeout_guard {\n            guard.cancel();\n        }\n        let status = status.expect(\"command wait must complete\");\n        if !status.success() {\n            if let Some(timeout) = timeout_guard.as_ref().and_then(|guard| guard.timed_out()) {\n                bail!(\"timed out after {timeout:?}\");\n            }\n            bail!(\"exited with non-zero status: {status}\");\n        }\n        Ok((\n            stdout_hasher.finalize().to_hex().to_string(),\n            stderr_hasher.finalize().to_hex().to_string(),\n        ))\n    }\n\n    /// Run the command and return stdout, even when raw mode is enabled.\n    pub(crate) async fn read(mut self) -> Result<String> {\n        let _read_lock = RAW_LOCK.read().await;\n        debug!(\"$ {self}\");\n        self.cmd.kill_on_drop(true);\n        #[cfg(unix)]","sourceCodeStart":1353,"sourceCodeEnd":1389,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/cmd.rs#L1353-L1389","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: new branch breaks without recording status\nloop {\n    tokio::select! {\n        r = &mut wait, if status.is_none() => { status = Some(r?); break; }\n        output = rx.recv() => {\n            let Some(o) = output else { status = Some(wait.await?); break; };\n            if too_big(o) { break; } // BUG: status stays None -> panic\n        }\n    }\n}\n\n// after: every break completes the wait first\nif too_big(o) { status = Some(wait.await?); break; }","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["process","async","invariant","panic","internal","cmd"],"backgroundTag":"internal-invariant-violation","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}