Summary
OpenCode V2 starts a recursive Parcel watcher for every non-home Location, even when the Location is not a VCS project. On a large non-Git working directory this can leave tens of thousands of inotify watches alive after the 10-second subscription timeout, consume substantial kernel memory, and push a long-running host toward its memory ceiling.
This is the V2/Core continuation of the broader symptom reported in #23190. The concrete missing invariant here is that LocationWatcher should not register at all when location.vcs is absent.
Environment
- opencode version: V2 pinned at
8deb0e5780dc83160a9ec12e6476e36267f82948
- OS: Linux 6.12.93 x86_64
- Terminal: Not applicable; observed in a headless OpenCode Agent service
- Shell: Not applicable
- Install/channel: Immutable OpenCode Agent production release; OpenCode built from source
- Active plugins: Unavailable; no plugin is involved in watcher registration
Reproduction
- Start OpenCode V2 with a non-home, non-VCS Location containing a very large directory tree. The observed Location contained about 479,000 directories, including 131 task worktrees with about 468,000 directories under the worktree tree, but the Location root itself had no
.git entry.
- Allow Location services to boot.
LocationWatcher.node is included unconditionally in locationServices.
- Observe
LocationWatcher call watcher.subscribe({ path: location.directory, type: "directory", ... }) because the Location is not exactly $HOME; this branch does not check location.vcs.
- Wait for the 10-second subscription timeout and inspect
/proc/<pid>/fdinfo/*.
Expected Behavior
A Location without VCS metadata should not register the recursive location watcher. The Git metadata watcher is already conditional on location.vcs?.type === "git"; the broad Location watcher should preserve the same VCS-project boundary.
Actual Behavior
The broad Location subscription is unconditional for every Location except exact $HOME.
In the production incident:
- Startup logged
failed to subscribe for the non-Git Location after 10 seconds.
- The live service still had two inotify instances: 458 watches for config directories and 82,502 watches attributable to the pending broad Location subscription.
- During the incident investigation, the retained watch set was estimated to account for roughly 1 GiB of kernel slab at peak and contributed to the host approaching its memory ceiling.
- The timeout handler calls
pending.then(subscription => subscription.unsubscribe()), so cleanup cannot happen until Parcel's native recursive subscription finishes and resolves. The timeout does not cancel traversal or remove watches already installed while it remains pending.
Additional Context
The exact registration paths are:
- Config watcher: global config and discovered
.opencode directories.
- Location watcher: the entire Location recursively, with static ignore patterns.
- Git metadata watcher: separately guarded by
location.vcs?.type === "git".
Current consumers of filesystem.changed are skill-cache invalidation and web/desktop file-tree or VCS refresh. In the headless Agent runtime only skill invalidation consumes these events. Normal .opencode/skills changes are also covered by the config watcher through config.updated; broader configured skill paths can still use Location events inside actual VCS projects.
The smallest fix is an early return in LocationWatcher when location.vcs is absent, plus changing the existing test that currently asserts non-Git roots are watched.
Related issue: #23190 documented hangs in large non-Git directories and was closed automatically for inactivity.
Production evidence window: 2026-07-07 22:45 UTC through 2026-07-08 00:20 UTC.
Source discussion: https://discord.com/channels/1391832426048651334/1521893148220199002/1524176536159125636
OpenCode Agent diagnostic identities (for internal correlation):
- OpenCode Session:
ses_agent_6ecee8df-cd50-491b-ab66-83fac776da16
- AgentSession:
6ecee8df-cd50-491b-ab66-83fac776da16
- Triggering input:
slack:1783468015.936579
- Active OpenCode Agent release:
5e15fd408ea7609ab51eb9117dc84dc000d942c0_8deb0e5780dc83160a9ec12e6476e36267f82948
Summary
OpenCode V2 starts a recursive Parcel watcher for every non-home Location, even when the Location is not a VCS project. On a large non-Git working directory this can leave tens of thousands of inotify watches alive after the 10-second subscription timeout, consume substantial kernel memory, and push a long-running host toward its memory ceiling.
This is the V2/Core continuation of the broader symptom reported in #23190. The concrete missing invariant here is that
LocationWatchershould not register at all whenlocation.vcsis absent.Environment
8deb0e5780dc83160a9ec12e6476e36267f82948Reproduction
.gitentry.LocationWatcher.nodeis included unconditionally inlocationServices.LocationWatchercallwatcher.subscribe({ path: location.directory, type: "directory", ... })because the Location is not exactly$HOME; this branch does not checklocation.vcs./proc/<pid>/fdinfo/*.Expected Behavior
A Location without VCS metadata should not register the recursive location watcher. The Git metadata watcher is already conditional on
location.vcs?.type === "git"; the broad Location watcher should preserve the same VCS-project boundary.Actual Behavior
The broad Location subscription is unconditional for every Location except exact
$HOME.In the production incident:
failed to subscribefor the non-Git Location after 10 seconds.pending.then(subscription => subscription.unsubscribe()), so cleanup cannot happen until Parcel's native recursive subscription finishes and resolves. The timeout does not cancel traversal or remove watches already installed while it remains pending.Additional Context
The exact registration paths are:
.opencodedirectories.location.vcs?.type === "git".Current consumers of
filesystem.changedare skill-cache invalidation and web/desktop file-tree or VCS refresh. In the headless Agent runtime only skill invalidation consumes these events. Normal.opencode/skillschanges are also covered by the config watcher throughconfig.updated; broader configured skill paths can still use Location events inside actual VCS projects.The smallest fix is an early return in
LocationWatcherwhenlocation.vcsis absent, plus changing the existing test that currently asserts non-Git roots are watched.Related issue: #23190 documented hangs in large non-Git directories and was closed automatically for inactivity.
Production evidence window: 2026-07-07 22:45 UTC through 2026-07-08 00:20 UTC.
Source discussion: https://discord.com/channels/1391832426048651334/1521893148220199002/1524176536159125636
OpenCode Agent diagnostic identities (for internal correlation):
ses_agent_6ecee8df-cd50-491b-ab66-83fac776da166ecee8df-cd50-491b-ab66-83fac776da16slack:1783468015.9365795e15fd408ea7609ab51eb9117dc84dc000d942c0_8deb0e5780dc83160a9ec12e6476e36267f82948