gleam-lang/gleam · error

Final result error writing

Error message

Final result error writing

What it means

In compiler-cli's main(), after Command::parse().run() returns Err, the error report is rendered to a buffered stderr and printed with expect("Final result error writing"). If that write fails (EPIPE/EIO on fd 2), the process panics — masking the real compilation error that was about to be printed. So you get an exit-by-panic plus a lost diagnostic whenever gleam fails AND stderr is unwritable at the same time.

Source

Thrown at compiler-cli/src/lib.rs:941

        version: String,
    },
}

pub fn main() {
    initialise_logger();
    panic::add_handler();
    let stderr = cli::stderr_buffer_writer();
    let result = get_current_directory()
        .and_then(|working_directory| Command::parse().run(working_directory));
    match result {
        Ok(_) => {
            tracing::info!("Successfully completed");
        }
        Err(error) => {
            tracing::error!(error = ?error, "Failed");
            let mut buffer = stderr.buffer();
            error.pretty(&mut buffer);
            stderr.print(&buffer).expect("Final result error writing");
            std::process::exit(1);
        }
    }
}

fn command_check(paths: &ProjectPaths, target: Option<Target>) -> Result<()> {
    let _ = build::main(
        paths,
        Options {
            root_target_support: TargetSupport::Enforced,
            warnings_as_errors: false,
            codegen: Codegen::DepsOnly,
            compile: Compile::All,
            mode: Mode::Dev,
            target,
            no_print_progress: false,
        },
        build::download_dependencies(paths, cli::Reporter::new())?,

View on GitHub (pinned to 7e623aa83d)

Solutions

  1. Redirect stderr to a file: `gleam build 2>err.log` — the error report always lands somewhere readable.
  2. Avoid `2>&1 | head` on failing commands; run the pipe over a complete capture: `gleam build >out.log 2>&1` then inspect the log.
  3. Re-run the command with stderr attached to a terminal to see the real Error::pretty diagnostic.
  4. Note the exit code: the panic aborts before std::process::exit(1), so the shell sees the panic exit path (101) instead of 1 — scripts should treat any nonzero code as failure.

Example fix

# before: real error is swallowed, second panic on stderr write
gleam publish 2>&1 | head -n 1

# after: capture everything, then read it
gleam publish >publish.log 2>&1; status=$?; head -n 50 publish.log; exit $status
Defensive patterns

Strategy: try-catch

Validate before calling

# ensure fd 2 is open and a valid destination before running gleam
gleam build 2>/dev/null  # NO — this discards errors; prefer:
gleam build 2>err.log || { cat err.log >&2; exit 1; }

Try / catch

// Wrap the CLI as a subprocess: a panic (including this expect) becomes a
// non-zero exit status you can observe instead of crashing your host.
let status = std::process::Command::new("gleam").args(args).stderr(std::process::Stdio::piped()).output()?;
if !status.status.success() {
    eprintln!("gleam failed:\n{}", String::from_utf8_lossy(&status.stderr));
}
// Embedding main() directly? use std::panic::catch_unwind(AssertUnwindSafe(|| main()))

Prevention

When it happens

Trigger: A failing gleam command with stderr piped to a reader that already exited: `gleam build 2>&1 | head -n1` where the build errors after head quit; fd 2 closed with `2>&-`; stderr redirected to a dead pseudo-terminal or socket (detached daemon, killed CI step).

Common situations: CI scripts that pipe all output through head/grep -q and then check exit codes; wrapper tools that close both pipes on first error; nohup/disowned processes whose terminal vanished.

Related errors


AI-assisted analysis of gleam-lang/gleam@7e623aa83d (2026-08-17). Data as JSON: /api/errors/35aaec8a4f9e58ae. Report an issue: GitHub.