Skip to content

fix(runtime): surface and satisfy Grep's ripgrep dependency #5167

Description

@colaforniaw

Problem

On machines without ripgrep — most non-developer machines, plenty of developer ones — every Grep call fails with a bare Error: spawn rg ENOENT: no cause, no fix.

The root cause is structural: Grep shells out to an external rg binary that nothing ships; resolution only probes PATH plus a few well-known install locations (defaultRipgrepCandidates() in launch-spec.ts).

Git archaeology says this is migration residue, not design:

Desired outcome

Two independently shippable phases.

Phase 1 — actionable failure (small, do first)

  • In the local executor's grep, convert spawn ENOENT (and only that — confirm the failing command is rg) into a local error with a stable identifier and a message that names ripgrep and carries a cross-platform install hint (brew install ripgrep + the official install page). Local resolution happens per call, so a fresh install works without a session restart; the worker path, where resolution is launch-time, should additionally hint at a restart.
  • Preserve semantics: exit code 1 stays "no matches"; EACCES/ENOEXEC/timeouts/signals keep their original meaning instead of being misreported as "not installed".
  • Unify user-visible copy across the local path and the worker's grep_unavailable (both should name ripgrep and how to fix it); keep the Windows-sandbox message ("use Glob and Read instead") separate — that is a capability limit, not a missing dependency.
  • Tests: ENOENT converts; exit code 1 unchanged; other spawn errors pass through.
  • Out of scope: any in-process or find/awk substitute (breaks the advertised ripgrep contract), startup preflight (PATH changes mid-session; noisy for sessions that never grep), protocol enum changes.

Phase 2 — ship rg with the product (product decision, own review)

Make every distribution channel able to satisfy the rg dependency itself, keeping the Phase 1 error as last line of defense (bundled copies can still go missing — AV quarantine, broken installs; Claude Code hit exactly this in some channels). Prior art, verified against source:

Project Channel Approach Verified in
VS Code desktop bundles rg in app resources the precedent users never notice
Claude Code (Agent SDK) npm + desktop vendored vendor/ripgrep/<platform> + COPYING paseo's after-pack.js operates on this exact layout
paseo Electron desktop prunes the SDK's vendored rg to the running platform in after-pack.js per-target Electron bundling, direct precedent
opencode CLI system rg first → pinned-version runtime download from official BurntSushi releases (pins 15.1.0, 7-platform asset matrix, caches under its bin dir) packages/core/src/ripgrep/binary.ts
pi CLI system probe (rg --version) → download on demand tools-manager.ts; even maps Termux's ripgrep package
Codex Rust agent sidesteps it: codex-file-search is a self-contained compiled binary on the ignore crate + nucleo; content search via shell, no Grep-like contract to fail codex-rs/file-search

Two shippable routes, per channel:

  • Desktop: vendor the binary. Ship the platform rg in extraResources (the windows-sandbox.exe entry is the existing per-platform pattern) and prepend the packaged path to defaultRipgrepCandidates(), PATH/system fallbacks retained. Prune to the running platform, as paseo does. License: rg is MIT OR Unlicense — Category-A bundling into this Apache-2.0 project; ship the license texts under licenses/ripgrep/ (same pattern as licenses/electron/LICENSE) and pin the release artifact sha256 (same pattern as releaseToolchain does for Node). Estimated one-time cost ≈ 1–2 eng-days incl. 3-platform CI/signing verification; ongoing cost is a binary bump roughly twice a year.
  • CLI (optional): pinned runtime download. opencode/pi-style: resolve system rg, else download the pinned official release to a managed bin dir on first use. Fills the gap between "user installs manually" and "vendored"; degrades to the Phase 1 error when offline. Deterministic-build concerns that rule out @vscode/ripgrep's postinstall do not apply here — the download is per-machine and failure-aware, not install-time and silent.

Prerequisite for the vendor route: the local executor must consume the resolved candidate list — today it spawns bare rg from PATH and cannot see a bundled copy. Resolution unification lands before or with Phase 2; the Phase 1 error remains the last line of defense in all channels.

Alternatives or workarounds

  • Rely on the README Requirement ("ripgrep, used by Runtime's Grep tool") — reasonable for source-build developers, a dead end for packaged desktop on non-developer machines.
  • Reintroduce find/awk fallbacks — rejected by the feat(runtime): apply one-shot permissions to sandboxed file tools #983/feat(windows): add brokered AppContainer sandbox support #2961 fail-closed contract; "available but semantically different" reported as success is worse than an honest failure. Contrast: DeepSeek-Reasonix keeps a native Go scanner and warns explicitly when rg is requested but absent — workable there because its search contract is self-defined; ours advertises ripgrep semantics, so a silent substitute would misreport.
  • @vscode/ripgrep postinstall download — adds a network dependency to installs and weakens build determinism; vendoring (desktop) or failure-aware first-use download (CLI) fit better.
  • Startup preflight / UI diagnostics — out of scope for this fix; if wanted later, as an explicit diagnostics surface, not a blocking check.

Open question for the thread: sequencing — does Phase 2 wait on a call about desktop's target audience, and should the CLI's runtime-download route be in scope at all, or does the CLI stay README-requirements territory? My lean: Phase 1 lands regardless; desktop vendor needs a maintainer decision; CLI download can ride behind it.

AI use: Maka (AI agent) assisted with git archaeology, source inspection, and a survey of local agent repositories (opencode, pi, paseo, DeepSeek-Reasonix) for prior art; the analysis and conclusions are my own.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions