xai-org/grok-build · error

failed to spawn acp-stdin reader thread

Error message

failed to spawn acp-stdin reader thread

What it means

spawn_stdin_line_reader spawns an OS thread that forwards stdin lines to the ACP pipeline; it calls .expect("failed to spawn acp-stdin reader thread"), so the process panics if std::thread::spawn fails. Spawn failure means the OS refused to create a thread - almost always resource exhaustion.

Source

Thrown at crates/codegen/xai-acp-lib/src/stdin_reader.rs:101

    // parks in a blocking read holding the global `StdinLock`. After this, any
    // other `std::io::stdin()` read in the process EOFs immediately instead of
    // deadlocking. `None` means we couldn't isolate (we fall back to reading
    // `std::io::stdin()` directly — no worse than before).
    #[cfg(windows)]
    let private_stdin: Option<std::fs::File> = isolate_process_stdin();

    std::thread::Builder::new()
        .name("acp-stdin".to_string())
        .spawn(move || {
            #[cfg(windows)]
            if let Some(file) = private_stdin {
                forward_lines(std::io::BufReader::new(file), &tx);
                return;
            }
            let stdin = std::io::stdin();
            forward_lines(stdin.lock(), &tx);
        })
        .expect("failed to spawn acp-stdin reader thread");
    rx
}

/// Read `\n`-delimited lines from `reader` and forward each on `tx` — via
/// [`normalize_json_line`], so bytes are verbatim except for the lines that
/// workaround rewrites (terminator always preserved) — until EOF, a read
/// error, or the receiver is dropped.
fn forward_lines<R: BufRead>(mut reader: R, tx: &mpsc::Sender<Vec<u8>>) {
    let mut line = Vec::new();
    loop {
        line.clear();
        match reader.read_until(b'\n', &mut line) {
            // EOF or a fatal read error: return, dropping `tx` closes the channel.
            Ok(0) | Err(_) => break,
            Ok(_) => {}
        }
        let normalized = normalize_json_line(std::mem::take(&mut line));
        // `blocking_send` parks this thread (not a runtime worker) when the

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Raise limits: ulimit -u / ulimit -v, or the container's pids.max / RLIMIT_NPROC
  2. Audit and fix thread leaks so the process is not at its thread cap at startup
  3. Wrap startup in catch_unwind or pre-flight check resource headroom and fail with a clear message
  4. Reduce concurrent subsystems spawning threads before ACP initialization

Example fix

// before
let rx = spawn_stdin_line_reader(); // panics under thread exhaustion
// after
let rx = std::panic::catch_unwind(spawn_stdin_line_reader)
    .map_err(|_| anyhow!("cannot spawn stdin reader: thread limit reached"))?;
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

let rx = std::panic::catch_unwind(spawn_stdin_line_reader)
    .map_err(|_| anyhow!("stdin reader thread could not be spawned (resource limits?)"))?;

Prevention

When it happens

Trigger: Calling spawn_stdin_line_reader() when the process has hit the thread/process rlimit (ulimit -u), the OS is out of memory for a new stack, or cgroup/pid limits (pids.max) are exhausted.

Common situations: Containers with low pids cgroup limits; CI machines with restrictive ulimits; a thread leak elsewhere in the app exhausting the allowance before ACP startup.

Related errors


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