{"record":{"id":"2384192c0b0120a4","repo":"xai-org/grok-build","slug":"headless-stdout-write-failed","errorCode":null,"errorMessage":"headless: stdout write failed","messagePattern":"headless: stdout write failed","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"critical","filePath":"crates/codegen/xai-grok-pager/src/headless.rs","lineNumber":1341,"sourceCode":"                    // Log rather than swallow: a serialize failure would drop the frozen spend fields.\n                    Err(e) => tracing::warn!(\n                        error = %e,\n                        \"headless: failed to serialize prompt-error usage; spend fields dropped\"\n                    ),\n                }\n            }\n            let stop_reason_override =\n                (xai_grok_shell::sampling::error::stop_reason_for_turn_error(&err) == \"MaxTokens\")\n                    .then_some(\"max_tokens\");\n            emitter.on_error(&msg, stop_reason_override);\n            Err(anyhow::anyhow!(\"{msg}\"))\n        }\n        None => Ok(()),\n    };\n\n    // A hard stdout write error outranks the normal outcome: output is dead, so exit non-zero.\n    if let Some(err) = emitter.take_output_error() {\n        return Err(anyhow::Error::new(err).context(\"headless: stdout write failed\"));\n    }\n    outcome\n}\n\n/// Background work tracked for exit: bash/monitor tasks and background subagents, keyed by id.\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\nenum BackgroundWork {\n    Task(String),\n    Subagent(String),\n}\n\n/// Ext request that kills one unit of background work (subagent cancel or task kill).\nfn reap_request_for_work(\n    work: &BackgroundWork,\n    session_id: &acp::SessionId,\n) -> serde_json::Result<acp::ExtRequest> {\n    let (method, params) = match work {\n        BackgroundWork::Subagent(id) => (","sourceCodeStart":1323,"sourceCodeEnd":1359,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-pager/src/headless.rs#L1323-L1359","documentation":"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.","triggerScenarios":"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.","commonSituations":"Piping headless output into a program that exits early; running with stdout redirected to a full filesystem; container capturing output after shutdown.","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"],"exampleFix":"// before\nopencode --print | head -n 1   # head exits, EPIPE kills output\n// after\nopencode --print > out.txt     # durable sink for full output","handlingStrategy":"try-catch","validationCode":"// ensure stdout is writable before starting a headless turn\nuse std::io::Write;\nstd::io::stdout().flush().expect(\"stdout must be writable\");","typeGuard":null,"tryCatchPattern":"match run_single_turn(...) {\n  Err(e) if e.to_string().contains(\"headless: stdout write failed\") => {\n    eprintln!(\"output sink died: {e:#}\"); // inspect cause chain for io error\n  }\n  r => r,\n}","preventionTips":["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"],"tags":["headless","stdout","io","epipe"],"backgroundTag":"stdout-write-failed","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}