xai-org/grok-build · error

Failed to capture ripgrep stdout

Error message

Failed to capture ripgrep stdout

What it means

Immediately after spawning ripgrep, content_search_streaming takes ownership of the child's stdout. If stdout is somehow absent (take() returns None), this error is thrown. In practice spawn() was configured with piped stdout, so this is a defensive check that should rarely fire.

Source

Thrown at crates/codegen/xai-grok-workspace/src/file_system/content.rs:151

    params: &ContentSearchParams,
    on_status: F,
) -> anyhow::Result<ContentSearchData>
where
    F: Fn(ContentSearchBatch) + Send + 'static,
{
    let max_files = params.max_files.unwrap_or(DEFAULT_MAX_FILES);
    let max_matches = params.max_matches.unwrap_or(DEFAULT_MAX_MATCHES);

    let mut cmd = build_ripgrep_command(root, params);
    #[allow(clippy::disallowed_methods)] // waited on below; killed on drop (cancellation)
    let mut child = cmd
        .spawn()
        .map_err(|e| anyhow::anyhow!("Failed to spawn ripgrep: {}", e))?;

    let stdout = child
        .stdout
        .take()
        .ok_or_else(|| anyhow::anyhow!("Failed to capture ripgrep stdout"))?;

    let mut reader = BufReader::new(stdout).lines();
    let mut files: Vec<ContentMatchFile> = Vec::new();
    let mut current_file: Option<ContentMatchFile> = None;
    let mut total_matches = 0usize;
    let mut pending_files: Vec<ContentMatchFile> = Vec::new();
    let mut last_notify = Instant::now();
    let mut hit_limit = false;

    while let Ok(Some(line)) = reader.next_line().await {
        if line.is_empty() {
            continue;
        }

        let json: serde_json::Value = match serde_json::from_str(&line) {
            Ok(v) => v,
            Err(_) => continue,
        };

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Inspect build_ripgrep_command and ensure it sets `.stdout(Stdio::piped())`.
  2. Re-run — if transient (pipe creation failure), retrying usually works.
  3. Check sandbox/SELinux/seccomp policies that may block anonymous pipes for child processes.

Example fix

// before: stdout not piped
Command::new("rg").args(&args).spawn()
// after
Command::new("rg").args(&args)
    .stdout(std::process::Stdio::piped())
    .stderr(std::process::Stdio::piped())
    .spawn()
Defensive patterns

Strategy: try-catch

Validate before calling

fn ripgrep_piped() -> bool {
    std::process::Command::new("rg")
        .arg("--version")
        .stdout(std::process::Stdio::piped())
        .output()
        .map(|o| o.status.success())
        .unwrap_or(false)
}

Try / catch

match content_search_streaming(root, params).await {
    Ok(m) => m,
    Err(e) if e.to_string() == "Failed to capture ripgrep stdout" => {
        eprintln!("internal error: ripgrep stdout not piped; check build_ripgrep_command");
        fallback_search(root, params)
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling content_search_streaming when the child process was spawned without Stdio::piped() for stdout (e.g. build_ripgrep_command changed or was overridden), or a platform anomaly where the pipe could not be created.

Common situations: A modification to build_ripgrep_command that switched stdout to Stdio::inherit() or null; unusual sandboxed environments that deny pipe creation; forks/wrappers of the command builder.

Related errors


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