sinelaw/fresh · error

Failed to wait for command

Error message

Failed to wait for command: {}

What it means

After spawning the shell, run_shell_command_blocking calls child.wait() to collect the exit status. Failure to wait (the child handle's stdin/stdout pipes are broken or the process data is unreadable) yields this error wrapping the io::Error; note this is not the same as the command exiting non-zero.

Solutions

  1. Check the wrapped io::Error; BrokenPipe/ECHILD usually means the child died externally — treat it as a non-fatal command failure
  2. Re-run the command after restoring the terminal (the code re-enters the alternate screen afterward)
  3. Avoid killing the editor's process group while a shell command is running
  4. Consider using waitpid semantics/try_wait and reporting the exit status instead of erroring on reap failure

Example fix

// before
let status = child.wait()
    .map_err(|e| anyhow::anyhow!("Failed to wait for command: {}", e))?;
// after
let status = child.wait().unwrap_or_else(|e| {
    log::warn!("failed to reap shell command: {e}");
    std::process::exit_status_approximation::failure()
});
Defensive patterns

Strategy: try-catch

Try / catch

// treat reap failure as a soft failure, not a fatal error
let status = match child.wait() {
    Ok(s) => s,
    Err(e) => {
        log::warn!("could not reap shell command: {e}");
        return Ok(1); // assume failure exit code
    }
};

Prevention

When it happens

Trigger: child.wait() returns an io::Error — typically because the child process was reaped elsewhere, its pipes closed unexpectedly, or the process was killed in a way that invalidates the handle (SIGKILL of the process group, terminal teardown).

Common situations: User kills the shell's process group (Ctrl-C / kill -9) while a long command runs; terminal session drops over ssh mid-command; the process outlives the parent's ability to reap it.

Related errors


AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13). Data as JSON: /api/errors/b471b2ad8524186e. Report an issue: GitHub.

Appendix: source

Thrown at crates/fresh-editor/src/app/shell_command.rs:272

        use crossterm::ExecutableCommand;
        use std::io::stdout;

        // Suspend TUI — best-effort, nothing useful to do on failure.
        #[allow(clippy::let_underscore_must_use)]
        let _ = disable_raw_mode();
        #[allow(clippy::let_underscore_must_use)]
        let _ = stdout().execute(LeaveAlternateScreen);

        let shell = detect_shell();
        let mut child = Command::new(&shell)
            .args(["-c", command])
            .hide_window()
            .spawn()
            .map_err(|e| anyhow::anyhow!("Failed to spawn shell: {}", e))?;

        let status = child
            .wait()
            .map_err(|e| anyhow::anyhow!("Failed to wait for command: {}", e))?;

        // Resume TUI — best-effort, nothing useful to do on failure.
        #[allow(clippy::let_underscore_must_use)]
        let _ = stdout().execute(EnterAlternateScreen);
        #[allow(clippy::let_underscore_must_use)]
        let _ = enable_raw_mode();

        // Request a full hard redraw to clear any ghost text from the external command
        self.request_full_redraw();

        if status.success() {
            Ok(())
        } else {
            anyhow::bail!("Command failed with exit code: {:?}", status.code())
        }
    }
}

View on GitHub (pinned to 67894ca546)