facebook/flow · error

failed to flush cli errors

Error message

failed to flush cli errors

What it means

After printing the collected error/warning report to a locked stdout handle, the flow status-style command flushes the buffer with `out.flush().expect("failed to flush cli errors")`. The panic fires when the flush itself returns an IO error, which in practice means the process on the other end of stdout has gone away (EPIPE/BrokenPipe) or stdout was closed. Rust ignores SIGPIPE by default, so instead of dying silently the write error surfaces here as a panic.

Source

Thrown at rust_port/crates/flow_cli/src/status_command.rs:201

                out.flush().expect("failed to flush vim/emacs errors");
            } else {
                let mut cli_errors = errors.clone();
                for (error, _) in &suppressed_errors {
                    cli_errors.add(error.clone());
                }
                let stdout = std::io::stdout();
                let mut out = stdout.lock();
                flow_common_errors::error_utils::cli_output::print_errors(
                    &mut out,
                    error_flags,
                    &None,
                    strip_root.as_deref(),
                    &cli_errors,
                    &warnings,
                    lazy_msg.as_deref(),
                )
                .expect("failed to write cli errors");
                out.flush().expect("failed to flush cli errors");
            }
            flow_common_exit_status::exit(command_utils::get_check_or_status_exit_code(
                &errors,
                &warnings,
                error_flags.max_warnings,
            ))
        }
        server_prot::response::StatusResponse::NO_ERRORS => {
            if args.output_json {
                print_json(
                    &ConcreteLocPrintableErrorSet::empty(),
                    &ConcreteLocPrintableErrorSet::empty(),
                    &[],
                )
            } else {
                println!("No errors!");
                if let Some(msg) = &lazy_msg {
                    println!("\n{}", msg);

View on GitHub (pinned to f88ac94bcf)

Solutions

  1. Avoid killing the reader early: write to a file or terminal (`flow status > report.txt`) instead of piping into head/grep -m.
  2. If scripting, consume the full output or redirect to a file so the pipe never breaks mid-flush.
  3. If you own the code, treat BrokenPipe as a clean exit: match on `out.flush()` and exit 0 on ErrorKind::BrokenPipe instead of expect.
  4. In wrapper processes, restore default SIGPIPE behavior (`libc::signal(SIGPIPE, SIG_DFL)`) so the process exits quietly rather than panicking.

Example fix

// before
out.flush().expect("failed to flush cli errors");

// after
if let Err(e) = out.flush() {
    if e.kind() != std::io::ErrorKind::BrokenPipe {
        panic!("failed to flush cli errors: {e}");
    }
    std::process::exit(0); // reader went away; report was effectively delivered
}
Defensive patterns

Strategy: try-catch

Try / catch

let flush_result = out.flush();
if let Err(e) = flush_result {
    if e.kind() == std::io::ErrorKind::BrokenPipe {
        std::process::exit(0); // reader went away; treat as success
    }
    panic!("failed to flush cli errors: {e}");
}

Prevention

When it happens

Trigger: Running the command with stdout piped to a short-lived reader that exits before all error output is written: `flow status | head`, `flow check | grep -m1 pattern`, a pager the user quits early, or a log collector that closes the pipe. Also triggered by deliberately closing fd 1 (`>&-`).

Common situations: Shell scripts and CI pipelines that pipe flow CLI output into head/less/grep; wrapper processes that close stdout before the command finishes; large error dumps that outlive a `head -n 10` reader.

Related errors


AI-assisted analysis of facebook/flow@f88ac94bcf (2026-08-20). Data as JSON: /api/errors/00f22ef9e4a25cee. Report an issue: GitHub.