Skip to content

watcher: fs watcher subscribes/unsubscribes skill directories in a tight loop (100%+ CPU, macOS) #53811

Description

@bbruen3

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 (homebrew opencode serve --service)
  • OS: macOS 26.6.2 (25G83) / Darwin 25.6.0, arm64
  • Terminal: desktop app (OpenChamber); TERM=xterm-256color, TERM_PROGRAM empty
  • Shell: /bin/zsh
  • Install/channel: release/latest
  • Active plugins: OpenChamber injects -opencode.browser (disabled) + /Users/<user>/.config/openchamber/agent-tool/openchamber-agent-tool; global opencode.json plugin is unset.

Reproduction

  1. On macOS, have several skill roots present, e.g. ~/.claude/skills, ~/.agents/skills, and project-level <project>/.claude/skills + <project>/.agents/skills.
  2. 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 with OPENCHAMBER_OPENCODE_CWD=$HOME), and open a project.
  3. Watch CPU: top/ps shows the process climbing to ~250–320%.
  4. Watch the log: ~/.local/share/opencode/log/opencode.log grows ~500+ lines/s, ~94% of them watcher subscribe / watcher started / watcher stopped for the skill roots.
  5. 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions