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

  1. Ensure the stdout consumer stays alive for the whole turn (avoid `cmd | head` truncation kills)
  2. Check disk space/permissions on the redirect target
  3. Check earlier logs for the underlying io error stored in the emitter
  4. 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

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


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/2384192c0b0120a4. Report an issue: GitHub.