openai / openai/codex

VS Code extension can root a recursive "**" file watcher at an arbitrary path outside the workspace (e.g. $HOME), causing system-wide FSEvents overload

Open
#44,718 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug extension performance
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:

  1. Open VS Code with a workspace folder that does not contain the Codex working-tree / task directory.
  2. Point a Codex task at a directory outside the workspace folder (a broad one such as $HOME is the worst case).
  3. 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:

  1. 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 when jSt() returns null) instead of wl.Uri.file(t).
  2. Refuse unbounded roots — bail out for $HOME, /, and filesystem roots, the way VS Code itself refuses non-recursive watches outside the workspace.
  3. Bound the walk — a max depth and/or always exclude the usual high-churn trees (**/Library/Caches/**, **/node_modules/**, **/target/**, **/.git/objects/**).
  4. 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 $HOME as a workspace." I scanned all 799 workspaceStorage/*/workspace.json records (764 unique workspace roots) plus every .code-workspace on 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.chatgpt and johnstoncode.svn-scm. (The scan is textual, so it could in principle miss a watcher built some other way.) The latter roots its RepositoryFilesWatcher at 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.jsensureWatching(), 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.chatgpt and restarting (11:04:15), fileWatcher.log is 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 created 2026-09-11 09:51:09 and its build version 26.908.31457 encodes 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 an fs_usage/lsof trace.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.