VS Code extension can root a recursive "**" file watcher at an arbitrary path outside the workspace (e.g. $HOME), causing system-wide FSEvents overload
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
What version of the IDE extension are you using?
openai.chatgpt-26.908.31457-darwin-arm64
What subscription do you have?
No ChatGPT subscription — API usage only
Which IDE are you using?
VS Code 1.134.0
What platform is your computer?
Darwin 25.5.0 arm64 arm (macOS 26.5.2, MacBookPro17,1 / Apple M1 / 16 GB)
What issue are you seeing?
The extension's working-tree file watcher can root a recursive ** watch at an arbitrary absolute path that is outside every workspace folder — including the user's home directory. On macOS the resulting FSEvents load drove my machine into repeated full-filesystem re-scans, then a hard freeze that required a forced power-off. Twice in two days.
(Note: absolute paths in this report are redacted — /Users/<user> is the home directory and <workspace> is a project folder name. Code snippets and log excerpts are otherwise verbatim.)
Observed symptom (system level)
VS Code's own file-watcher log recorded a recursive watcher rooted at the home directory, not at the opened workspace. 70 errors in the final 4 minutes 19 seconds before the freeze, all with the same root:
2026-09-11 09:58:04.634 [error] [File Watcher ('parcel')] Events were dropped by the FSEvents client. File system must be re-scanned. (path: /Users/<user>)
2026-09-11 09:58:07.188 [error] [File Watcher ('parcel')] Events were dropped by the FSEvents client. File system must be re-scanned. (path: /Users/<user>)
...
2026-09-11 10:02:23.390 [error] [File Watcher ('parcel')] Events were dropped by the FSEvents client. File system must be re-scanned. (path: /Users/<user>)
In VS Code, the (path: ...) suffix is the watcher request's root (out/vs/platform/files/node/watcher/watcherMain.js):
toMessage(t, r) { return r ? `[File Watcher ('parcel')] ${t} (path: ${r.path})` : ... }
The workspace that was actually open was ~/svn/<workspace> — not the home directory. Counters for that session: 43 "too many file change events" warnings, 70 "must be re-scanned" errors, 70 of them rooted at /Users/<user>.
The event bursts that overflowed the FSEvents client were 1082 – 4513 events in a single batch; the logged culprit files were Go build-cache writes:
2026-09-11 09:58:14.542 [warning] [File Watcher ('parcel')] started ignoring events due to too many file change events at once (incoming: 3860, most recent change: /Users/<user>/Library/Caches/go-build/00/001187dcdb953e789f7a95205a021fc74a466ea00cfec55bf32f532ce6cda64c-a). Use 'files.watcherExclude' setting to exclude folders with lots of changing files (e.g. compilation output).
Consequence: each overflow forced a re-scan of an unbounded tree, and the machine went from load average 2 to 105–164 on 8 cores, mds_stores/apfsd/fileproviderd/duetexpertd all hit system resource limits, and the UI stopped responding. There was no kernel panic — the machine had to be hard-powered-off:
$ cat /Library/Logs/DiagnosticReports/ResetCounter-2026-09-11-100401.diag
Reset count: 0
Boot failure count: 1
Boot faults: btn_rst,btn_seq_reset timeout,dblclick_timeout
$ last reboot
reboot time Fri Sep 11 10:03
reboot time Wed Sep 9 17:00
reboot time Wed Sep 9 16:39
Timeline for the last occurrence — VS Code launched 09:50:21, last log line written at 10:02:23, machine dead ~10:02:30, rebooted 10:03:35 (12m 12s of uptime). The Codex.log for that session ends at the same instant:
2026-09-11 10:02:38.758 [warning] [IpcClient] Received broadcast but no handler is configured method=ipc-connection-reset
Two separate Code/ZCode Helper microstackshot reports from the same period record ~2147 MB of dirty file-backed memory written in minutes, with the sampled stack sitting in a single-threaded write():
Event: disk writes
Writes: 2147.49 MB of file backed memory dirtied over 492 seconds (4365.01 KB per second average)
198 thread_start
198 _pthread_start
198 uv_cancel + 556 (Electron Framework)
196 write + 8 (libsystem_kernel.dylib)
What steps can reproduce the bug?
I could not reduce this to a minimal standalone reproduction, but the code path is narrow enough to point at directly.
The extension is openai.chatgpt-26.908.31457. It calls createFileSystemWatcher exactly once, and that one call is an unbounded recursive watch:
// out/extension.js — vscode-working-tree-watch
async function Ike(t, e) {
if (!t.isLocal || hr()) return t.startFileWatch(e);
let r = jSt(e.path); // <-- resolves the watch root
if (r == null) return t.startFileWatch(e);
try {
let n = wl.workspace.createFileSystemWatcher(new wl.RelativePattern(r, "**")), ...
return { coverage: { recursive: true, typedPathChanges: false }, path: e.path, ... };
jSt() decides that root. Its fallback branch returns the raw absolute path with no requirement that it be inside a workspace folder:
function jSt(t) {
let e = wl.workspace.workspaceFolders,
r = e?.find(o => Pke(o.uri.fsPath, t)); // a workspace folder containing t?
if (r != null) return Tke(r.uri) ? Ake(r.uri, t) : null;
let n = e?.filter(o => o.uri.scheme === "vscode-remote" && Pke(t, o.uri.fsPath));
if (n != null && n.length > 0) { let [o] = n; return o == null || n.some(...) ? null : Ake(o.uri, t); }
return e?.some(o => o.uri.scheme === "vscode-remote" || !Tke(o.uri)) ? null : wl.Uri.file(t);
// ^^^^^^^^^^^^^^^
// fallback: watch the caller-supplied path verbatim, whatever it is
}
So whenever the working-tree path is not contained in any workspace folder, the extension roots a "**" recursive watcher at that path — with no bound, no depth limit, and no refusal for $HOME or /.
The path comes from the caller:
() => o && this.options.startWorkingTreeWatch != null
? this.options.startWorkingTreeWatch(s, i?.directories ?? [])
: ...
I was not able to capture the concrete value passed in (the extension does not log the resolved base), so I cannot prove the /Users/<user> root came from this call site — see "Attribution" below. What I can show is that this code path permits it.
Reproduction attempt, for anyone who wants to confirm:
- Open VS Code with a workspace folder that does not contain the Codex working-tree / task directory.
- Point a Codex task at a directory outside the workspace folder (a broad one such as
$HOMEis the worst case). - Watch for a watcher request whose root is that directory:
~/Library/Application Support/Code/logs/<session>/window1/fileWatcher.log— look for(path: <that directory>).
What is the expected behavior?
A recursive "**" watcher should never be rooted outside the workspace folder(s) without an explicit, bounded opt-in. Concretely, at least one of:
- Clamp the root to the workspace folder — if the working-tree path is not inside any workspace folder, fall back to
startFileWatch(e)(which the code already does whenjSt()returnsnull) instead ofwl.Uri.file(t). - Refuse unbounded roots — bail out for
$HOME,/, and filesystem roots, the way VS Code itself refuses non-recursive watches outside the workspace. - Bound the walk — a max depth and/or always exclude the usual high-churn trees (
**/Library/Caches/**,**/node_modules/**,**/target/**,**/.git/objects/**). - Log the resolved base once at debug level, so this is diagnosable without reading the bundle. Its absence is why I cannot fully close the attribution below.
Additional information
Attribution — why I believe this extension, and where I stop
I ruled out the obvious explanations first:
-
It is not "the user opened
$HOMEas a workspace." I scanned all 799workspaceStorage/*/workspace.jsonrecords (764 unique workspace roots) plus every.code-workspaceon disk: the bare home directory is never among them. VS Code's own workspace watching can only watch workspace folders, so a/Users/<user>-rooted watch cannot come from it. -
It is not another extension. A static scan of all 66 installed extensions for the
RelativePattern(<base>, "**")watcher form returned exactly two hits:openai.chatgptandjohnstoncode.svn-scm. (The scan is textual, so it could in principle miss a watcher built some other way.) The latter roots itsRepositoryFilesWatcherat an SVN working-copy root, and there is no SVN working copy at$HOME(only~/.subversion, a config directory) — so it is ruled out here. -
VS Code's per-extension workspace gate does not cover this case. In
out/vs/workbench/api/node/extensionHostProcess.js→ensureWatching(), a non-recursive glob whose base is outside any workspace folder is refused outright, but a**recursive glob skips that check and is passed straight through:let c = r.pattern.includes('**'); // recursive? if (c && u.length === 0) { /* only applies files.watcherExclude */ } else if (!c) { ...; if (!m || m.length === 0) return l; } // refused if outside workspace return d.$watch(session, r.baseUri, { recursive: c, excludes: u, includes: m, filter: g }, !!a);So nothing upstream stops a recursive watch from escaping the workspace.
-
Uninstalling the extension removed the symptom. After removing
openai.chatgptand restarting (11:04:15),fileWatcher.logis not created at all, no process holds an fd on/Users/<user>, and over the following 28 minutes (past the 12m 12s mark where it previously died) load stayed at 2–7 with 0 watcher errors and 0 files churned under~/Library. Also relevant: the extension directory was created2026-09-11 09:51:09and its build version26.908.31457encodes 2026-09-08 — matching the two days the problem appeared.
Honest caveat: this is elimination plus a matching code path, not a smoking gun. Two conditions are needed: (1) a home-rooted recursive watch and (2) a high file-write rate as fuel. On the day I verified, condition (2) was absent, so I cannot claim the green result isolates the extension. I would rather you tell me which call site sets this root than have me overstate it.
Other notes
- No kernel panic, no thermal shutdown, no jetsam kill — this was resource starvation (I/O + CPU + memory compressor), so it presents to users as "my Mac overheats and freezes," not as a crash report from Codex.
- Related existing issues, same class of "watch scope escapes its intended tree":
- #23574 — VS Code Codex extension can allocate about 1M inotify watches on Linux large workspace
- #30795 — Skill watcher follows directory symlinks and can recurse into huge external trees
- If it helps, I can reinstall the extension and run a
$HOME-scoped Codex task to capture the resolved watch root directly, or provide anfs_usage/lsoftrace.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with out/extension.js, tracing Ike and jSt where createFileSystemWatcher receives a RelativePattern rooted at the working-tree path. Compare this with VS Code's ensureWatching() behavior in out/vs/workbench/api/node/extensionHostProcess.js, then reproduce using a task directory outside the workspace and inspect fileWatcher.log. Done means an out-of-workspace recursive watch is safely rejected or bounded, with coverage for the behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, vscode
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100