Schniz/fnm · error

Can't write output

Error message

Can't write output

What it means

The `outln!` macro writes one line to the writer selected by log level (stdout for info, stderr for error-level output, a sink for quiet mode) and unwraps the result. If the underlying write fails, the process panics with 'Can't write output'. The classic cause is EPIPE: Rust ignores SIGPIPE, so when a downstream reader like `head` closes the pipe, the next write returns a broken-pipe error instead of killing the process silently.

Source

Thrown at src/log_level.rs:54

        }
    }

    pub fn possible_values() -> &'static [&'static str; 4] {
        &["quiet", "info", "all", "error"]
    }
}

impl Display for LogLevel {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.as_str())
    }
}

#[macro_export]
macro_rules! outln {
    ($config:ident, $level:path, $($expr:expr),+) => {{
        use $crate::log_level::LogLevel::*;
        writeln!($config.log_level().writer_for($level), $($expr),+).expect("Can't write output");
    }}
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_is_writable() {
        assert!(!LogLevel::Quiet.is_writable(LogLevel::Info));
        assert!(!LogLevel::Error.is_writable(LogLevel::Info));
        assert!(LogLevel::Info.is_writable(LogLevel::Info));
        assert!(LogLevel::Info.is_writable(LogLevel::Error));
    }
}

View on GitHub (pinned to 86adc9676c)

Solutions

  1. Consume the full output instead of closing the reader early: capture to a file/variable (`fnm env > /tmp/env.sh`, `out=$(fnm env)`) and slice afterwards.
  2. In wrappers, tolerate the exit: `fnm env | head -1 || true`.
  3. Use quiet mode (`--quiet` / log-level quiet) when output is unwanted, routing writes to the sink so no failing write occurs.
  4. If patching fnm: match on the write result and ignore `ErrorKind::BrokenPipe` (standard CLI behavior).

Example fix

// before (src/log_level.rs)
writeln!($config.log_level().writer_for($level), $($expr),+).expect("Can't write output");

// after
let _ = writeln!($config.log_level().writer_for($level), $($expr),+)
    .inspect_err(|e| if e.kind() != std::io::ErrorKind::BrokenPipe { panic!("Can't write output: {e}") });
Defensive patterns

Strategy: try-catch

Try / catch

use std::io::Write;
let mut w = config.log_level().writer_for(LogLevel::Info);
if let Err(e) = writeln!(w, "{}", line) {
    if e.kind() == std::io::ErrorKind::BrokenPipe {
        std::process::exit(0); // reader went away; exit quietly like other CLIs
    }
    panic!("Can't write output: {e}");
}

Prevention

When it happens

Trigger: Piping fnm output to a short-lived reader: `fnm env | head -1`, `fnm ls | grep -m1 pattern`, `fnm env --json | jq '.PATH'` where jq exits early; or redirecting to a device/file that fails writes (`fnm install 20 >/dev/full`, a read-only redirect).

Common situations: Shell scripts and dotfiles piping `fnm env` into `head`/`sed -n 1p`; editor plugins that open fnm, read a few lines, and close the pipe; log collectors closing stdin/stdout; writing to a full disk via redirection.

Related errors


AI-assisted analysis of Schniz/fnm@86adc9676c (2026-08-16). Data as JSON: /api/errors/c0963ba29773bd6e. Report an issue: GitHub.