You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
#110 / #117: Grep/Glob "prefer ripgrep, fall back to find/awk" — a missing rg was invisible.
feat(runtime): apply one-shot permissions to sandboxed file tools #983 (2026-07): the sandboxed worker established the fail-closed contract — rg's regex dialect, gitignore behavior, glob and truncation semantics have no in-process substitute; missing rg must surface as grep_unavailable, not a degraded result.
feat(windows): add brokered AppContainer sandbox support #2961 (Windows AppContainer): hardened the local path (shell-string → execFile) but copied its error handling verbatim (only exit code 1), while the same commit gave the worker the fail-closed check. This is the exact commit where the two paths' failure handling forked — the local path's bare ENOENT is incomplete migration from the fallback era.
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.
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.
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
rgbinary that nothing ships; resolution only probesPATHplus a few well-known install locations (defaultRipgrepCandidates()inlaunch-spec.ts).Git archaeology says this is migration residue, not design:
grep_unavailable, not a degraded result.execFile) but copied its error handling verbatim (only exit code 1), while the same commit gave the worker the fail-closed check. This is the exact commit where the two paths' failure handling forked — the local path's bare ENOENT is incomplete migration from the fallback era.Desired outcome
Two independently shippable phases.
Phase 1 — actionable failure (small, do first)
ENOENT(and only that — confirm the failing command isrg) 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.EACCES/ENOEXEC/timeouts/signals keep their original meaning instead of being misreported as "not installed".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.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:
vendor/ripgrep/<platform>+COPYINGafter-pack.jsoperates on this exact layoutafter-pack.jsrgfirst → 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.tsrg --version) → download on demandtools-manager.ts; even maps Termux'sripgreppackagecodex-file-searchis a self-contained compiled binary on theignorecrate +nucleo; content search via shell, no Grep-like contract to failcodex-rs/file-searchTwo shippable routes, per channel:
rginextraResources(thewindows-sandbox.exeentry is the existing per-platform pattern) and prepend the packaged path todefaultRipgrepCandidates(), 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 underlicenses/ripgrep/(same pattern aslicenses/electron/LICENSE) and pin the release artifact sha256 (same pattern asreleaseToolchaindoes 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.@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
rgfromPATHand 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
rgis requested but absent — workable there because its search contract is self-defined; ours advertises ripgrep semantics, so a silent substitute would misreport.@vscode/ripgreppostinstall download — adds a network dependency to installs and weakens build determinism; vendoring (desktop) or failure-aware first-use download (CLI) fit better.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.