imsnif/bandwhich · error

Failed to write to stdout

Error message

Failed to write to stdout: {e}

What it means

bandwhich writes its report snapshots to stdout from a dedicated thread. If a write to stdout fails and it is not a benign BrokenPipe (consumer exited), the code panics with this message rather than continuing with a broken output stream.

Solutions

  1. Ensure the consumer of bandwhich stdout (e.g. `bandwhich --raw | consumer`) stays alive and reads stdout continuously
  2. Check disk space / filesystem health if stdout is redirected to a file
  3. Run bandwhich in a terminal or with a valid pipe, not with a closed stdout (`bandwhich >&-`)
  4. If piping programmatically, handle BrokenPipe as normal shutdown and treat other io::Error kinds as fatal

Example fix

// before
bandwhich | grep -m1 eth0
// after
bandwhich --raw | while read line; do grep -m1 eth0 <<< "$line" && break; done; # keep reader alive until match
Defensive patterns

Strategy: try-catch

Validate before calling

// Rust (caller): only pipe when a live reader exists
use std::io::IsTerminal;
let safe = std::io::stdout().is_terminal() || reader_is_alive();
if !safe { eprintln!("stdout has no live reader; use --raw with a consuming pipe"); }

Type guard

fn stdout_is_writable() -> bool {
    use std::io::Write;
    let mut out = std::io::stdout();
    out.write_all(b"").is_ok()
}

Try / catch

match stdout.write_all(&bytes) {
    Ok(_) => {}
    Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => std::process::exit(0),
    Err(e) => eprintln!("stdout write failed: {e}"), // log & exit cleanly instead of panicking
}

Prevention

When it happens

Trigger: Calling `create_write_to_stdout`'s spawned write task when stdout is closed, redirected to a failing file/pipe with a non-broken-pipe error (e.g. ENOSPC, EPIPE variant with different kind, EIO on a detached terminal), or the receiver ends of `stdout_write` are dropped.

Common situations: Piping bandwhich into a process that dies mid-run without a clean SIGPIPE, running with stdout redirected to a full disk, running under a supervisor that closes stdout, or running in environments (CI, systemd) where stdout is detached.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of imsnif/bandwhich@1899870cea (2026-09-08). Data as JSON: /api/errors/102f298c17dc0d72. Report an issue: GitHub.

Appendix: source

Thrown at src/os/shared.rs:103

}

fn get_interface(interface_name: &str) -> Option<NetworkInterface> {
    datalink::interfaces()
        .into_iter()
        .find(|iface| iface.name == interface_name)
}

fn create_write_to_stdout() -> Box<dyn FnMut(&str) + Send> {
    let mut stdout = io::stdout();
    Box::new({
        move |output| match writeln!(stdout, "{output}") {
            Ok(_) => (),
            Err(e) if e.kind() == ErrorKind::BrokenPipe => {
                // A process that was listening to bandwhich stdout has exited
                // We can't do much here, lets just exit as well
                std::process::exit(0)
            }
            Err(e) => panic!("Failed to write to stdout: {e}"),
        }
    })
}

pub fn get_input(
    interface_name: Option<&str>,
    resolve: bool,
    dns_server: Option<Ipv4Addr>,
) -> eyre::Result<OsInputOutput> {
    // get the user's requested interface, if any
    // IDEA: allow requesting multiple interfaces
    let requested_interfaces = interface_name
        .map(|name| get_interface(name).ok_or_else(|| eyre!("Cannot find interface {name}")))
        .transpose()?
        .map(|interface| vec![interface]);

    // take the user's requested interfaces (or all interfaces), and filter for up ones
    let available_interfaces = requested_interfaces

View on GitHub (pinned to 1899870cea)