dbt-labs/dbt-core · error

failed to write to stdout

Error message

failed to write to stdout

What it means

The TUI layer writes pending-skip output lines to stdout with write_all and unwraps with .expect("failed to write to stdout"). Rust std write_all returns Err when the underlying stdout is closed or unwritable (e.g. broken pipe), and this expect turns that into a panic.

Source

Thrown at crates/dbt-common/src/tracing/layers/tui_layer.rs:260

                    skipped.seen_unit_test,
                    true,
                ));

                // Clear the pending names and flags
                skipped.pending_names.clear();
                skipped.seen_test = false;
                skipped.seen_unit_test = false;
            }
        },
    );

    // Emit the output after the span lock has been released to avoid possible deadlocks
    if let Some(output) = output_to_emit {
        tui.write_suspended(|| {
            io::stdout()
                .lock()
                .write_all(format!("{}\n", output).as_bytes())
                .expect("failed to write to stdout");
        });
    }
}

fn node_evaluated_progress_status(
    span_status: Option<&SpanStatus>,
    ne: &NodeEvaluated,
) -> Option<&'static str> {
    match ne.node_outcome() {
        NodeOutcome::Success if get_test_outcome(ne.into()) == Some(TestOutcome::Failed) => {
            Some("failed")
        }
        NodeOutcome::Success => Some("succeeded"),
        NodeOutcome::Error => Some("failed"),
        NodeOutcome::Canceled => Some("cancelled"),
        NodeOutcome::Skipped => match ne.node_skip_reason() {
            NodeSkipReason::Cached => Some("reused"),
            NodeSkipReason::NoOp => Some("no-op"),

View on GitHub (pinned to 0267ce9170)

Solutions

  1. Avoid piping dbt output to commands that exit early, or run without the TUI layer (use --log-format json/text with a stable sink)
  2. Check disk space and that the output file/pipe target is writable
  3. In code, handle io::Error instead of .expect — specifically ignore io::ErrorKind::BrokenPipe

Example fix

// before
io::stdout()
    .lock()
    .write_all(format!("{}\n", output).as_bytes())
    .expect("failed to write to stdout");
// after
if let Err(e) = io::stdout().lock().write_all(format!("{}\n", output).as_bytes()) {
    if e.kind() != io::ErrorKind::BrokenPipe {
        eprintln!("failed to write to stdout: {e}");
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before relying on stdout, verify it is writable
use std::io::IsTerminal;
if !io::stdout().is_terminal() {
    eprintln!("stdout is redirected; ensure the consumer stays alive for the whole run");
}

Try / catch

match io::stdout().lock().write_all(bytes) {
    Ok(()) => {}
    Err(e) if e.kind() == io::ErrorKind::BrokenPipe => { /* consumer gone; stop writing */ }
    Err(e) => eprintln!("stdout write failed: {e}"),
}

Prevention

When it happens

Trigger: emit_pending_skips fires on span end / node processed while stdout has been closed or its reader exited — e.g. `dbt ... | head`, redirection to a full disk, or a supervisor that closed the pipe.

Common situations: Piping dbt output into `head`/`less -F` that exits early, running dbt under CI systems that kill the log stream, or stdout redirected to a file on a full filesystem.

Related errors


AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07). Data as JSON: /api/errors/26cbc03fbef788fe. Report an issue: GitHub.