Repository navigation
watcher: fs watcher subscribes/unsubscribes skill directories in a tight loop (100%+ CPU, macOS) #53811
Description
Summary
On macOS, an opencode serve process (v2.0.21, also observed on v2.0.24) enters a tight, self-sustaining subscribe/unsubscribe loop in the native filesystem watcher, pegging a core and more (~250–320% CPU). The churn is entirely @parcel/watcher's FSEvents backend re-subscribing over skill directories — with zero filesystem changes, so it is not driven by any file activity. Setting OPENCODE_DISABLE_FILEWATCHER=1 stops it instantly.
Environment
- opencode version: 2.0.21 (
.opencode/bin/opencode, launched by OpenChamber) — also reproduced on 2.0.24 (homebrewopencode serve --service) - OS: macOS 26.6.2 (25G83) / Darwin 25.6.0, arm64
- Terminal: desktop app (OpenChamber);
TERM=xterm-256color,TERM_PROGRAMempty - Shell:
/bin/zsh - Install/channel: release/latest
- Active plugins: OpenChamber injects
-opencode.browser(disabled) +/Users/<user>/.config/openchamber/agent-tool/openchamber-agent-tool; globalopencode.jsonpluginis unset.
Reproduction
- On macOS, have several skill roots present, e.g.
~/.claude/skills,~/.agents/skills, and project-level<project>/.claude/skills+<project>/.agents/skills. - Start a server that boots locations for those directories, e.g.
opencode serve --hostname 127.0.0.1 --port <p>with cwd$HOME(OpenChamber launches it withOPENCHAMBER_OPENCODE_CWD=$HOME), and open a project. - Watch CPU:
top/psshows the process climbing to ~250–320%. - Watch the log:
~/.local/share/opencode/log/opencode.loggrows ~500+ lines/s, ~94% of themwatcher subscribe/watcher started/watcher stoppedfor the skill roots. sample <pid>shows the time is entirely in the native watcher.
Expected: watchers subscribe once and then sit idle; CPU near zero.
Actual: continuous subscribe → started → stopped → subscribe churn; CPU stays pegged until the process is restarted.
Reproduces consistently on every fresh server launch, with no user interaction and no filesystem changes.
Expected Behavior
Skill-directory watchers should subscribe once, stay subscribed, and idle. No resubscription should occur absent filesystem activity.
Actual Behavior
The watcher loops. Samples show only @parcel/watcher frames (.bun-501-*.node):
FSEventsBackend::startStream → FSEventStreamStart (register_with_server / f2d_register_rpc)
FSEventsBackend::unsubscribe → FSEventStreamStop (dispose_f2d_private_port / f2d_unregister_rpc)
Watcher::triggerCallbacks → Watcher::isIgnored → Glob::isIgnored
Backend::watch / Backend::unwatch
Log growth during a 6-second window:
total lines: 2808
message="watcher subscribe": high
message="watcher stopped": ~257/4s
message="watcher started": ~257/4s
non-watcher lines: 0
Watched paths, all repeated:
~/.claude/skills
~/.agents/skills
<project>/.claude/skills
<project>/.agents/skills
watcher subscribe entries always carry ignores=0 (the skill dirs have no ignore patterns).
Not caused by file changes: a 6-second snapshot of every file/dir mtime under those four roots was byte-identical (diff empty), while the churn continued uninterrupted. So this is a replay/self-trigger loop inside the watcher, not a reaction to writes.
Additional Context
Disabling the file watcher stops it (A/B, same machine/paths):
| Run | Server CPU | log lines/4s |
|---|---|---|
| watcher enabled | 187% | 1701, 1787 |
OPENCODE_DISABLE_FILEWATCHER=1 (relaunch) |
12–18% | 45, 0, 0 |
Env confirmed on the child: OPENCODE_DISABLE_FILEWATCHER=1, OPENCODE_FILEWATCHER_DISABLE=1. In the bundle these gate fs.filewatcher:
fs: { filewatcher: !truthy(process.env.OPENCODE_FILEWATCHER_DISABLE ?? process.env.OPENCODE_DISABLE_FILEWATCHER), fff: ... } — and the @opencode/Watcher service takes an enabled option.
Not the FFF watcher: #32511 root-causes a similar high-CPU symptom to the bundled FFF indexer (@ff-labs/fff-bun, notify-rs). Ours is the Bun/parcel watcher (FSEventsBackend), and OPENCODE_DISABLE_FILEWATCHER=1 (not OPENCODE_DISABLE_FFF) removes it. Worth noting the server here is launched with cwd $HOME, i.e. exactly #32511's trigger, so both problems may coexist.
Related issues: #32511 (high CPU; also flags an fsevents watcher duplication + re-subscribe amplifier), #37111 (@parcel/watcher subscribe under multi-project attach — deadlock, Linux), #35813 (closed; recursive parcel watcher per non-home Location), #21452 (parcel watcher callback shared across subscriptions).
Workaround: OPENCODE_DISABLE_FILEWATCHER=1 opencode serve ... — confirmed to stop the churn (loses live file-tree/hot-reload only). Also OPENCODE_DISABLE_FFF=1 is worth trying since the server runs from $HOME.
Happy to attach full sample output, the raw log window, and lsof on request.
Activity
opencode-agent commentedon Oct 7, 2026
Thanks for the detailed report and the A/B numbers.
We tried to reproduce this on 2.0.24 on Linux with the same layout:
~/.claude/skillsand~/.agents/skills, plus.claude/skillsand.agents/skillsin a project. We ranopencode servefrom$HOMEand opened both the project and$HOME. On Linux each skill directory was watched once and never re-subscribed, and CPU stayed near zero. Your samples are all inside the macOS FSEvents watcher, so this looks specific to macOS. It needs a maintainer with a Mac to check further.Two things would help:
opencode serve(without OpenChamber) from$HOMEon macOS?~/.config/opencode(for example, plugin state or log files)? A write anywhere in that directory can trigger the same skill-watcher re-subscribe cycle; see core: any file write inside the config directory triggers an un-debounced compatibility reload — ~/.claude/skills watcher resubscribe loop (v2.0.23) #53393.Related issues, ordered by likelihood:
~/.claude/skills, with high idle CPU over time$HOME, which may also apply to your setupUntil then,
OPENCODE_DISABLE_FILEWATCHER=1remains the workaround, as you found.bbruen3 commentedon Oct 7, 2026
Thanks — both answers:
Plain
opencode serve(no OpenChamber) from$HOME, macOS: no loop. v2.0.21opencode servewith SSE clients on$HOMEand a project stayed ~0.5% CPU, while the client-driven server on the same machine was at ~67% over the same window. So plain serve alone does not reproduce for me.Writes under
~/.config/opencodeduring churn: none. I monitored the config root,~/.config/openchamber, and~/.local/state/opencodefor 30s during active churn: 0 changes. So #53393's write-trigger isn't active here — this looks like the zero-activity variant in #53416.Consolidating all skill roots to one real dir cut it ~4× (CPU 288% → ~50%, log lines 1701/4s → 408/4s) but did not stop it — still looping over 3 watched paths (one global canonical dir plus the project's
.claude/skillsand.agents/skills). The watcher is left enabled.Given the overlap, this may be the macOS instance of #53416.
Gladess1102 commentedon Oct 18, 2026
Confirming this still happens on 2.0.32 (desktop app, bundled
opencode-cli2.0.32), and adding a variant of the loop plus two side effects that I didn't see mentioned here.Different failure mode:
subscribefails instead of churningIn my case the loop is driven by subscribe errors rather than successful resubscribes. Same root, different entry point:
Two things worth noting:
fs.watch()on the exact same directory from a separate Node process and it succeeded (all four skill roots, including the one that was failing). So the original error is transient — it's the retry behaviour that makes it permanent.Impact
opencode-cliCPUopencode-cliRSSfailed to subscribeappeared 3796 times in one session (roughly 1–3/s at peak). The machine was unusable — not just high CPU, but memory pressure pushing everything else into swap.RSS did come back down (~816 MB) once the loop stopped on its own, so I don't think this is a permanent leak — but the balloon is large enough to take a 16 GB machine down while it lasts.
Quitting the desktop app does not clear it
opencode-cli serve --servicesurvives app quit as an orphan (PPID 1) and keeps the elevated RSS. Relaunching the app reconnects to that same orphan, so restarting the app looks like it does nothing. Onlypkill -f "opencode-cli serve --service"clears it. Might be worth handling separately — it makes this bug much harder to get out of.Trigger
Same as yours: multiple project directories open, several skill roots. Mine appeared spontaneously after long uptime.
OPENCODE_DISABLE_FILEWATCHER=1sounds like the right workaround — thanks for finding it, I'll use that.Happy to provide the raw log window or a
sampleif useful. macOS 26.5.1 / M1 / 16 GB.