Skip to content

watcher: recursive watcher follows Windows directory junctions and hangs sessions in workspaces with junction-mirrored trees #50463

Open

Description

@MoutranCorp

Summary

On Windows, the recursive file watcher (backend=node) follows directory junctions (reparse points) instead of treating them like symlinks. In a workspace containing many junctions that mirror large subtrees, the watcher's effective tree explodes into millions of entries, and sessions hang in "working" forever. The watcher should not descend into reparse points (git with core.symlinks=true already treats them as symlinks and does not descend), or at minimum watcher.ignore semantics should keep the watcher from recursively re-walking mirrored trees.

Environment

  • opencode version: v2.0.12
  • OS: Windows_NT 10.0.26200 (win32 x64)
  • Terminal: TERM=xterm-256color; TERM_PROGRAM/COLORTERM unset (Windows)
  • Shell: C:\Windows\system32\cmd.exe (opencode shell setting: powershell)
  • Install/channel: latest (release)
  • Active plugins: none found in config (~/.config/opencode/opencode.jsonc contains only {"shell": "powershell"})

Reproduction

Minimal:

  1. Create a workspace with a large directory big/ (~50k files is enough to observe the slowdown).
  2. Create two scaffold directories, each containing junctions that mirror big/ and its subdirectories:
    mklink /J repro\a\big repro\big and mklink /J repro\b\big repro\big (and a few more nested ones).
  3. Open the workspace in opencode and start a session.

Real-world trigger (my case): a Godot project (233k files / 143 GB) whose QA harness generates verification_project/mandatory_suite_project scaffold trees under artifacts/. Each scaffold re-links game/ subdirectories via junctions — 1,233 junctions total, mirroring game/.godot (12,146 files / 9 GB) and game/assets (15,630 files / 8 GB) dozens of times each.

Expected Behavior

The watcher skips directory junctions/reparse points when doing recursive discovery (consistent with git, which runs with -c core.symlinks=true and treats them as symlinks), so watcher startup cost is proportional to the real tree. Sessions start responding promptly.

Actual Behavior

  • Session requests stay in "working" indefinitely; the first token never arrives.
  • The log shows the watcher starting with zero ignores and full recursion, e.g.:
    message="watcher subscribe" path="...\\AGENTS.md" type=file ignores=0
    message="watcher started" path="...\\.git\\HEAD" type=file backend=node ignores=0
    
  • The shared background service thrashes and gets restarted; the client log accumulates uncaught exceptions:
    level=ERROR message="uncaught exception" cause="Error: EPIPE: broken pipe, write" origin=uncaughtException role=cli
    
  • Meanwhile git status in the same workspace completes in ~0.7 s, because git does not descend through the junctions — which isolates the problem to the watcher's traversal, not workspace size per se.

Additional Context

  • Frequency: consistent — every session in this workspace, on v2.0.12.
  • Workaround that resolves it: adding a watcher.ignore block in opencode.jsonc covering the junction-heavy directories ("artifacts/**", "game/.godot/**", etc.). With those ignored, sessions respond normally. This works but must be maintained manually, and any new directory containing junctions reintroduces the hang.
  • Suggested fix: when doing recursive watch discovery on win32, skip entries with the FILE_ATTRIBUTE_REPARSE_POINT attribute (or only follow true symlinks), matching git's core.symlinks behavior. Optionally also seed watcher.ignore from .gitignore.
  • Side observation (same workspace): one junction pointed sideways at a sibling directory inside the watched tree (artifacts\hit_palette\mandatory_suite_project -> artifacts\hit_palette\verification_project_full), which makes naive traversal re-walk subtrees multiplicatively even without a cycle; a junction cycle would make it unbounded, so reparse-point skipping is the robust fix.

Activity

  1. github-actions commented on Sep 22, 2026

    @github-actions
    Contributor

    This issue might be related to an existing issue involving Windows reparse points and the file watcher. Please check:

    Neither is an exact duplicate of your report (junction-mirrored trees causing watcher explosion and session hangs), but they are in the same problem space and may be worth cross-referencing with maintainers.

  2. opencodejr commented on Oct 28, 2026

    @opencodejr
    Contributor

    Thanks for the detailed report and the junction setup. I couldn't reproduce this here: it depends on NTFS directory junctions, and our reproduction sandbox runs Linux, so it needs a maintainer on Windows.

    One note for whoever picks this up: in v2.0.12 the file watcher doesn't watch the workspace recursively. It only watches .git/HEAD and a few individual files, which is what the backend=node log lines show. The more likely thing walking the junctions is the built-in file-search index, which in v2.0.12 starts scanning the whole workspace when it opens. In later releases it waits until the first search, but it still scans the whole tree then. Snapshot capture before the model call is another possibility, but it goes through git, which doesn't follow junctions.

    If you can, it would help to know:

    • whether the hang still happens on the latest v2 release
    • whether the log shows anything about file search (fff) or snapshot capture around the time the session gets stuck

    Related issues:

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