xai-org/grok-build · critical · anyhow::Error
headless: stdout write failed
Error message
headless: stdout write failed
What it means
An anyhow error returned from headless run_single_turn when the stream emitter recorded a hard stdout write failure (take_output_error() returns Some). Because the output pipe is dead, the normal turn outcome is discarded and the process exits non-zero.
Source
Thrown at crates/codegen/xai-grok-pager/src/headless.rs:1341
// Log rather than swallow: a serialize failure would drop the frozen spend fields.
Err(e) => tracing::warn!(
error = %e,
"headless: failed to serialize prompt-error usage; spend fields dropped"
),
}
}
let stop_reason_override =
(xai_grok_shell::sampling::error::stop_reason_for_turn_error(&err) == "MaxTokens")
.then_some("max_tokens");
emitter.on_error(&msg, stop_reason_override);
Err(anyhow::anyhow!("{msg}"))
}
None => Ok(()),
};
// A hard stdout write error outranks the normal outcome: output is dead, so exit non-zero.
if let Some(err) = emitter.take_output_error() {
return Err(anyhow::Error::new(err).context("headless: stdout write failed"));
}
outcome
}
/// Background work tracked for exit: bash/monitor tasks and background subagents, keyed by id.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
enum BackgroundWork {
Task(String),
Subagent(String),
}
/// Ext request that kills one unit of background work (subagent cancel or task kill).
fn reap_request_for_work(
work: &BackgroundWork,
session_id: &acp::SessionId,
) -> serde_json::Result<acp::ExtRequest> {
let (method, params) = match work {
BackgroundWork::Subagent(id) => (View on GitHub (pinned to bc7f02eddd)
Solutions
- Ensure the stdout consumer stays alive for the whole turn (avoid `cmd | head` truncation kills)
- Check disk space/permissions on the redirect target
- Check earlier logs for the underlying io error stored in the emitter
- Retry the run once stdout is writable again
Example fix
// before opencode --print | head -n 1 # head exits, EPIPE kills output // after opencode --print > out.txt # durable sink for full output
Defensive patterns
Strategy: try-catch
Validate before calling
// ensure stdout is writable before starting a headless turn
use std::io::Write;
std::io::stdout().flush().expect("stdout must be writable"); Try / catch
match run_single_turn(...) {
Err(e) if e.to_string().contains("headless: stdout write failed") => {
eprintln!("output sink died: {e:#}"); // inspect cause chain for io error
}
r => r,
} Prevention
- Don't pipe headless output into consumers that exit early (e.g. `| head`)
- Redirect to a file with sufficient disk space
- Monitor EPIPE/broken-pipe behavior in CI harnesses
When it happens
Trigger: Writing streamed output to stdout fails — e.g. downstream consumer (piped process like `| head`) closed the pipe (EPIPE), disk/redirect target full or unwritable, or stdout closed.
Common situations: Piping headless output into a program that exits early; running with stdout redirected to a full filesystem; container capturing output after shutdown.
Related errors
- Headless mode requires a grok.com session. Run `grok login`
- command wrote more than {PROVIDER_STDOUT_CAP_BYTES} bytes to
- Failed to set working directory to {:?}: {}
- Failed to load config: {e}
- max turns reached
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/2384192c0b0120a4.
Report an issue: GitHub.