dbt-labs/dbt-core · error
failed to write header to stdout
Error message
failed to write header to stdout
What it means
handle_list_item_output writes the 'SELECTED NODES' delimiter header (once, guarded by an AtomicBool) using .expect('failed to write header to stdout'), panicking on write failure. The header write happens inside write_suspended while rendering `dbt list` output. A broken stdout makes listing output delivery fatal.
Source
Thrown at crates/dbt-common/src/tracing/layers/tui_layer.rs:1411
}
fn handle_list_item_output(&self, list_item: &ListItemOutput) {
if self.show_options.contains(&ShowOptions::Nodes) || self.command == FsCommand::List {
self.write_suspended(|| {
let mut stdout = io::stdout().lock();
// Only emit the decorative header when show_options is non-empty (i.e. not quiet).
// In quiet mode show_options is cleared, so the header is suppressed while list
// item content (the result payload) continues to be printed — matching dbt-core's
// PrintEvent behaviour where only decorative chrome is stripped.
if !self.show_options.is_empty()
&& !self.list_header_emitted.swap(true, Ordering::Relaxed)
{
let header =
format_delimiter(SELECTED_NODES_TITLE, self.max_term_line_width, true);
stdout
.write_all(format!("{}\n", header).as_bytes())
.expect("failed to write header to stdout");
}
// Print list item content (always — result payload survives quiet)
stdout
.write_all(format!("{}\n", list_item.content).as_bytes())
.expect("failed to write to stdout");
});
}
}
fn handle_show_data_output(&self, show_data: &ShowDataOutput) {
self.write_suspended(|| {
let mut stdout = io::stdout().lock();
stdout
.write_all(format!("{}\n", show_data.content).as_bytes())
.expect("failed to write show data to stdout");
});View on GitHub (pinned to 0267ce9170)
Solutions
- Consume the full pipe output or redirect to a file: `dbt list > nodes.txt`
- Use `dbt list | sed -n '1,5p'` alternatives carefully — prefer file redirection for truncation
- Verify the capturing process stays alive in CI
- Harden the layer to treat stdout errors as end-of-output rather than panic
Example fix
// before
.expect("failed to write header to stdout");
// after
if stdout.write_all(format!("{}\n", header).as_bytes()).is_err() {
return; // stdout gone; stop emitting
} Defensive patterns
Strategy: try-catch
Validate before calling
# capture the full listing to a file before truncating dbt list > nodes.txt 2>&1 && head -n 5 nodes.txt
Type guard
fn stdout_writable() -> bool {
use std::io::Write;
io::stdout().flush().is_ok()
} Try / catch
// catch pipe failure and fall back to file-based listing
dbt list 2>err.log || { grep -q 'failed to write header to stdout' err.log && dbt list > out.txt; } Prevention
- Never do `dbt list | head`; list to a file and head the file
- Count expected nodes first so truncation surprises are avoided
- Use tee if live filtering is required
- Ensure CI wrappers read all of dbt's output
When it happens
Trigger: First list-item log record after quiet filtering (on_log_record) triggers the header write while the pipe consumer has exited or stdout is invalid.
Common situations: `dbt list | head -n 5` — head exits after 5 lines and the next write fails; listing into a closed socket; redirected output on a full disk.
Related errors
- failed to write to stdout
- failed to write show data to stdout
- failed to write to stderr
- failed to write show result to stdout
- failed to write compiled code to stdout
AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07).
Data as JSON: /api/errors/f532a3053757e712.
Report an issue: GitHub.