[BUG] Runaway @vscode/ripgrep from extension chat-link resolution with multi-root workspace
Preflight Checklist
- [x] I have searched existing issues. The same symptom has been reported in #8973, #24778, #10979, and #24120; all four are closed (three by the inactivity bot, one as duplicate of #8973 which is itself bot-closed). Filing this as a new issue per the bot's own close-message instruction, with additional empirical evidence not present in the prior reports (see "What's new in this report" below).
- [x] This is a single bug report.
- [x] The bug reproduces on the most recent version available to me (extension
2.1.145; current marketplace latest is2.1.148, not yet retested on that version).
What's Wrong?
Summary. The anthropic.claude-code VS Code extension causes @vscode/ripgrep processes to run at near-full CPU saturation for many minutes whenever (a) the chat panel surfaces an absolute file path that does not exist on disk, and (b) the workspace has multiple roots. Two rg processes at 450–500% CPU each, alive for 10+ minutes when manually killed.
What's new in this report (vs #8973 / #24778 / #10979 / #24120):
- Reproduced the rg-level behavior independently of the extension, by running the same
@vscode/ripgrepbinary with the captured argv against the same workspace roots from a terminal. Confirms the runaway is deterministic given the argv. - Flag-isolation matrix identifying
--followas the dominant amplifier (~2000× slowdown).--no-ignorealone is fine;--followalone or in combination is what saturates CPU. This pinpoints exactly which behavior the workaround setting changes and explains its effectiveness. - Negative result for chat-render-only triggers: emitting markdown links to non-existent absolute paths in a single-root session did not spawn any rg processes, suggesting multi-root context is a precondition (not just panel render).
- Workaround quantified: with
search.followSymlinks: false, the rg-level command completes in ~27 ms instead of timing out at 60 s+.
What Should Happen?
When the extension's chat-link resolution layer encounters an absolute path that does not exist on disk, it should not pass that string to vscode.workspace.findFiles() (or the equivalent search API) as a -g include glob. The leading / does anchor at the search root (workspace root), not at filesystem root — but the include is still a literal path that cannot match anything, so ripgrep walks each workspace root in full evaluating files against an unmatchable include. With --follow (the VS Code default), that walk explodes through symlinks and exhausts CPU.
Unresolved chat-link paths should render as plain text within a bounded time.
Error Messages/Logs
Captured argv at the moment of the symptom (two sibling processes; personal paths masked):
$ ps -eo pid,pcpu,etime,args | grep '[r]ipgrep'
<PID-A> 503 09:45 /snap/code/<N>/usr/share/code/resources/app/node_modules/@vscode/ripgrep/bin/rg \
--files --hidden --case-sensitive --no-require-git \
--no-ignore --follow --no-config --no-ignore-global \
-g /nonexistent-root/example-dir/sub/file1.txt \
-g !**/.git -g !**/.svn -g !**/.hg -g !**/.DS_Store -g !**/Thumbs.db \
-g !/{**/node_modules,**/bower_components,...} \
[~15 KB of -g exclude patterns; the exclude blocks for **/__pycache__/**, **/.venv/**,
**/.claude/settings.local.json, etc. each appear 10–15 times back-to-back, suggesting
per-root search.exclude / files.exclude lists are concatenated without de-duplication]
<PID-B> 458 09:19 /snap/code/<N>/usr/share/code/resources/app/node_modules/@vscode/ripgrep/bin/rg \
--files --hidden ... -g /nonexistent-root/example-dir/sub/file2.json ...
Process tree (both rg processes are children of the VS Code extension host, siblings of the claude CLI process — so the spawn originates in extension JS code, not in the native claude binary):
code (extension host)
├── claude (CLI native binary)
├── claude (resumed session)
├── rg --files ... -g /nonexistent-root/example-dir/sub/file1.txt (503% CPU, 9m45s)
└── rg --files ... -g /nonexistent-root/example-dir/sub/file2.json (458% CPU, 9m19s)
Steps to Reproduce
Original observation (extension-level, not minimal):
- VS Code launched with a large multi-root workspace (my session had 21
--add-dirfolders passed to the Claude Code CLI on launch; see "Open questions" for why this count is common). anthropic.claude-codev2.1.145 active, chat panel open.- An agent emitted markdown containing absolute file path links under a directory that does not exist on the filesystem (an internal
/workspace/...style path that existed only in the agent's context model). - Without any click or hover from me, two
@vscode/ripgrepprocesses appeared as children of the VS Code extension host, each at 450–500% CPU. They remained alive until I killed them manually withkill <PID>.
Negative result (single-root + chat render alone, in a fresh session):
In a single-root Claude Code session (/home/<user>/workspace/Debugging, no --add-dir), I emitted markdown links to non-existent absolute paths ([x](/nonexistent-test-root-xyz/never-here/file.txt), and separately a batch of 5 such links) and observed ps aux | grep '@vscode/ripgrep': 0 rg processes spawned, before or after a 3 s wait. So chat-render alone is not sufficient; multi-root or some other condition is required.
Empirical rg-level repro (extension-free, terminal-only, deterministic):
To isolate ripgrep's behavior from the extension's trigger logic, I ran the bundled @vscode/ripgrep binary directly with the captured argv against the same workspace roots:
RG=/snap/code/<N>/usr/share/code/resources/app/node_modules/@vscode/ripgrep/bin/rg
ROOTS=$(ls -d /home/<user>/workspace/codechu-*) # 18 directories
# A: single root, full original flags
time timeout 30 "$RG" --files --hidden --case-sensitive --no-require-git \
--no-ignore --follow --no-config --no-ignore-global \
-g '/nonexistent-root/example-dir/sub/file1.txt' \
/home/<user>/workspace/codechu-cli-py
# → real 0m0.012s — instant
# B: 18 roots, full original flags
time timeout 60 "$RG" --files --hidden --case-sensitive --no-require-git \
--no-ignore --follow --no-config --no-ignore-global \
-g '/nonexistent-root/example-dir/sub/file1.txt' \
$ROOTS
# → real 1m0.003s (killed by timeout)
# user 0m46.028s, sys 9m43.117s — CPU saturated, load avg peaked above 7
Flag-isolation matrix (18 roots, same bad glob, varying flags):
| Flag combination | Wall time | CPU saturation |
|--------------------------------------|--------------------|----------------|
| --follow --no-ignore (as observed) | 60 s+ (timeout) | yes |
| --no-ignore only (no --follow) | 27 ms | no |
| --follow only (no --no-ignore) | 30 s+ (timeout) | yes |
| neither | 43 ms | no |
--follow is the dominant amplifier. Removing it cuts wall time by ~2000× on this workspace. This matches the established workaround ("search.followSymlinks": false) and explains why it is so effective: it strips the --follow flag from the VS Code-built argv.
Claude Model
Not model-dependent. The bug fires on any chat content that surfaces an absolute path string not present in any workspace root.
Is this a regression?
I don't know
Last Working Version
Unknown. The same symptom has been reported since at least 2025-11 (#10979), against multiple extension versions, so it appears present across recent builds.
Claude Code Version
claude --versionreports:2.1.47 (Claude Code)(this is the CLI build string)- npm package
@anthropic-ai/claude-codeversion: marketplace lists2.1.148as current latest - VS Code extension
anthropic.claude-code:2.1.145-linux-x64(the version on which the bug was observed; not yet retested on the marketplace-current2.1.148)
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux (Ubuntu 24.04.4 LTS, kernel 6.14; VS Code installed via snap, version 240)
Terminal/Shell
VS Code integrated terminal. The bug is not terminal-dependent — it originates in the extension host, not in the CLI shell.
Additional Information
Why a new issue rather than commenting on a prior one
Issues #8973, #24778, #10979, and #24120 are all closed and locked (no comment access). The most recent closure message on #8973 reads: "please open a new issue with updated information." This report adds the rg-level repro, flag-isolation matrix, and quantified workaround effectiveness that were not present in those reports.
Suspected root cause
Inferred from the captured argv; the extension source is not public, so this is hypothesis from outside the source.
- Non-matching literal include forces a full tree walk. The extension forwards the raw chat-string path (e.g.
/workspace/...) to a workspace-search invocation as a-ginclude glob. In ripgrep's gitignore-style glob syntax, the leading/anchors at the search root (workspace root), not at filesystem root — so rg looks under each workspace root for<root>/workspace/.... When that subpath does not exist in any root, nothing ever matches, but rg must walk every file in every root to evaluate the include against each path. There is no stat fast-path for literal-path include patterns. --followenlarges the walked tree catastrophically. Empirical: 18 roots ×--follow= CPU saturated for 60 s+; 18 roots without--follow= 27 ms. Symlink-loop detection in rg is O(parents-per-symlink) (BurntSushi/ripgrep#633), and many real-world workspaces have inter-directory symlinks (shared infra between sibling repos, vendor dirs, etc.) that turn the walk into a vastly larger graph.- Multi-root invocation amplifies linearly (or worse). VS Code's search service spawns one rg per workspace root with no pooling (microsoft/vscode#128054). N add-dirs → up to N simultaneous rg processes, each doing the full walk independently.
- Exclude-list duplication in the argv. The ~15 KB
-gargv shows the same exclude blocks (**/__pycache__/**,**/.venv/**,**/.claude/settings.local.json, etc.) repeated 10–15 times back-to-back. Whether this is the extension's doing or VS Code's search service concatenating per-root excludes is unclear from outside; in either case it is not the trigger but bloats argv and per-file pattern-matching cost.
The argv flags --no-config --no-ignore-global and the -g !**/... exclude shape are characteristic of VS Code's searchService built argv; I have not verified whether the extension uses vscode.workspace.findFiles() or spawns @vscode/ripgrep directly with its own mimicking flags. The argv shape strongly suggests the former, but a maintainer can confirm in seconds.
Workaround for affected users (verified to mitigate, not fix)
In user settings.json:
{
"search.followSymlinks": false,
"search.useIgnoreFiles": true
}
Combined with pkill -f '@vscode/ripgrep/bin/rg' for any in-flight runaway processes.
search.followSymlinks: false is the load-bearing setting; on my 18-root workspace it reduces the rg-level command from 60 s+ (CPU saturated) to ~27 ms. This setting flows through VS Code's searchService and strips --follow from the argv built for findFiles callers, so it reaches the extension's invocations.
The leading-/ malformed include itself is not addressed by any user-side setting — only by an extension-side fix.
Suggested fix (behavioral spec, not a patch — extension source is closed)
Wherever the extension resolves a chat-rendered path string into a workspace-search invocation, the resolution should:
- If the string is absolute, perform an existence check (by any appropriate means —
fs.access,vscode.workspace.fs.stat, etc.) before invoking workspace search. If the path exists, return its URI directly; no search is needed. - If the absolute path does not exist, do not pass it to
findFilesas a-ginclude. Either normalize it to a workspace-relative pattern that has a plausible chance of matching, or render the chat link as plain text (unresolved). The current behavior of forwarding a non-existent literal absolute path forces ripgrep into the full-tree-walk pathology above. - If the string is relative, resolve against workspace roots normally.
Defense-in-depth suggestions (in addition to the above):
- De-duplicate the merged
search.exclude/files.excludelist before invocation, if the extension is the one building it. - Ensure that any cancellation token passed to
findFilesfor chat-link resolution actually aborts the underlying rg child process on cancel/timeout, rather than orphaning it. (VS Code'sfindFilesaccepts aCancellationToken; whether the current code path uses it for chat-link resolution is unknown to me.)
Open questions
- Is the trigger condition specifically multi-root, or could a single-root workspace fire it under conditions I did not test (e.g. session resume, panel re-hydration, focus events)?
- Is the spawn coming from a
vscode.workspace.findFiles()call, or from the extension spawning@vscode/ripgrepdirectly with mimicked flags? - Does the bug fire identically on the current marketplace version
2.1.148? I have not retested on that version. - Does the bug fire on non-snap VS Code installs (e.g. native
.deb)? My environment is snap-based.
I am happy to run further targeted tests if you can suggest specific conditions to vary (e.g. "open with N add-dirs and observe initial chat hydration"). The 18-root rg command above is a one-liner that any maintainer can paste to confirm the ripgrep-side behavior independently in under a minute.
Context on multi-root workspace size
The 18–21 add-dir count in my repro is not atypical for users working across split-repo "polyrepo" or "monorepo-of-monorepos" layouts where each subsystem lives in its own directory but daily work spans all of them. Several issues on the Claude Code tracker mention add-dir counts in this range. The bug is much less visible on 1–2 root workspaces because the per-root walk-cost dominates total time.
Related external context
- microsoft/vscode#128054 —
searchServicespawns one rg per workspace root with no pooling - microsoft/vscode#193927 — orphaned, unkillable rg from repeated searches
- BurntSushi/ripgrep#633, #692 —
--followsymlink-loop detection cost
---
Attribution
This report was investigated and drafted by a Claude Code agent (Claude Opus 4.7, 1M context) running inside the affected VS Code session, after the human user noticed CPU saturation and asked the agent to diagnose. The agent identified the runaway processes, inspected the captured argv, ran the rg-level isolation tests in a terminal, and drafted this report. The human user reviewed the draft, requested neutralization of tone, masked personal paths, and approved before filing.
Filed because the bug obstructs the agent's own working environment — Claude Code's user-facing performance is being degraded by Claude Code's extension behavior. Mentioning the dogfooding context only because it may help maintainers prioritize: the friction is observable to anyone running an agentic workflow against a real multi-repo workspace.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗