zeroclaw-labs/zeroclaw · error
Search timed out after {TIMEOUT_SECS} seconds.
Error message
Search timed out after {TIMEOUT_SECS} seconds. What it means
The content_search tool fell back to its internal pure-Rust walker (used when ripgrep `rg` is not available) and the recursive directory walk exceeded the fixed 30-second budget (TIMEOUT_SECS at crates/zeroclaw-tools/src/content_search.rs:14). check_internal_deadline is a cooperative cancellation check invoked before the search starts, at every directory visit, and per file; once Instant::now() passes the deadline computed at content_search.rs:292, the walk aborts with this error and partial results are discarded.
Source
Thrown at crates/zeroclaw-tools/src/content_search.rs:486
context_before,
context_after,
max_results,
deadline,
&mut raw_lines,
&mut results_seen,
)?;
Ok(format_line_output(
&raw_lines.join("\n"),
workspace_canon,
output_mode,
max_results,
))
}
fn check_internal_deadline(deadline: Instant) -> anyhow::Result<()> {
if Instant::now() >= deadline {
anyhow::bail!("Search timed out after {TIMEOUT_SECS} seconds.");
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn visit_internal_search_path(
path: &Path,
workspace_canon: &Path,
include: Option<&glob::Pattern>,
security: &SecurityPolicy,
regex: ®ex::Regex,
output_mode: &str,
context_before: usize,
context_after: usize,
max_results: usize,
deadline: Instant,
raw_lines: &mut Vec<String>,
results_seen: &mut usize,View on GitHub (pinned to 88bb9c8533)
Solutions
- Install ripgrep (`rg`) on the host/container so the fast external backend is used instead of the internal walker — the same 30s timeout applies but rg finishes orders of magnitude faster.
- Narrow the search path: search a subdirectory of the workspace instead of the workspace root.
- Pass an `include` glob (e.g. "*.rs") so internal_include_matches skips non-matching files before search_internal_file reads them.
- Split the search into multiple invocations, one per top-level directory, so each walk stays under 30 seconds.
- Reduce per-match work: lower max_results and avoid context lines on huge trees.
Example fix
// before search(path: "/workspace", pattern: "TODO", include: null) // -> internal walker walks every file, hits the 30s deadline, bails // after search(path: "/workspace/crates", pattern: "TODO", include: "*.rs") // plus install ripgrep so the rg backend handles the request
Defensive patterns
Strategy: retry
Validate before calling
// Estimate workload before searching: if ripgrep is absent and the tree
// is large, expect the 30s internal-walker deadline.
fn rg_available() -> bool {
std::process::Command::new("rg")
.arg("--version")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
fn count_files(dir: &std::path::Path, cap: usize) -> usize {
let mut n = 0;
if let Ok(rd) = std::fs::read_dir(dir) {
for e in rd.flatten() {
n += 1;
if n > cap { break; }
let p = e.path();
if p.is_dir() { n += count_files(&p, cap.saturating_sub(n)); }
}
}
n
}
// before invoking: require rg, or a small tree, or an include glob
assert!(rg_available() || count_files(root, 20_000) < 20_000 || include_glob.is_some()); Try / catch
match tool.execute(params).await {
Err(e) if e.to_string().contains("Search timed out after") => {
// deadline fired: retry once per top-level subdir, or with a
// tighter include glob; do not retry the same broad request
for sub in top_level_dirs(root) {
let _ = tool.execute(with_path(params, sub)).await;
}
}
other => other,
} Prevention
- Install ripgrep in every image/sandbox that runs the search tool
- Always pass an include glob for large workspaces
- Search subdirectories instead of the workspace root
- Avoid pathological regexes (nested quantifiers, huge alternations)
When it happens
Trigger: Invoking the content search tool with a broad search path (the whole workspace) and either no include glob or one that matches many files, on a machine where `rg` is not on PATH so the internal backend runs (content_search.rs:233). Large trees, network/slow filesystems, or expensive regexes push the visit_internal_search_path/search_internal_file steps past the 30s deadline and the next check_internal_deadline call bails.
Common situations: Agent sandboxes and minimal Docker images that omit ripgrep; searching a monorepo with node_modules/target/vendor directories included; case-insensitive or complex regexes over thousands of files; a workspace on NFS or a slow bind mount where canonicalize plus reads dominate the budget.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- ACP elicitation/create timed out after {timeout:?}
- ACP elicitation/create (multi) timed out after {timeout:?}
- ACP request_permission timed out after {:?}
- interaction followup token expired (id {interaction_id}, >15
- Mattermost WebSocket authentication handshake timed out
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/3fb48dd2504df282.
Report an issue: GitHub.