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 theView on GitHub (pinned to bc7f02eddd)
Solutions
- Raise limits: ulimit -u / ulimit -v, or the container's pids.max / RLIMIT_NPROC
- Audit and fix thread leaks so the process is not at its thread cap at startup
- Wrap startup in catch_unwind or pre-flight check resource headroom and fail with a clear message
- 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
- Check ulimit -u / cgroup pids.max before launching in constrained environments
- Fix thread leaks so the process has headroom at ACP startup
- Pre-warm threads early and fail fast at startup, not mid-session
- Monitor thread count in long-running processes
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
- spawn mermaid-render thread
- default reqwest client builds
- aws-lc-rs supports the default protocol versions
- set_client_name called more than once
- failed to build shared HTTP client
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/b66e616eb0ebe4ce.
Report an issue: GitHub.