Skip to content

fix(core): skip location watcher outside VCS projects #35813

Description

@opencode-agent

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

  1. 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.
  2. Allow Location services to boot. LocationWatcher.node is included unconditionally in locationServices.
  3. Observe LocationWatcher call watcher.subscribe({ path: location.directory, type: "directory", ... }) because the Location is not exactly $HOME; this branch does not check location.vcs.
  4. 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

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    2.0bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions