fix(mcp): skip fs.watch on WSL2 /mnt drives that hang MCP startup (#199) (#210)

Recursive fs.watch on a WSL2 /mnt NTFS/9p mount walks the directory tree
with every readdir/stat crossing the Windows boundary, stalling the event
loop long enough to blow past opencode's 30s MCP handshake timeout so the
tools never appear. This is the file-watcher half of the #172 fix, which
moved the DB/WASM open off the handshake but left the watcher on the
critical path.

- Add watchDisabledReason() policy: CODEGRAPH_NO_WATCH (off) >
  CODEGRAPH_FORCE_WATCH (force on) > WSL2 + /mnt auto-detect (off).
  FileWatcher.start() and the MCP server both honor it; the server now
  logs why watching is off and how to refresh.
- Add `codegraph serve --mcp --no-watch`.
- When watching is off, init/install offer git sync hooks (post-commit,
  post-merge, post-checkout) that run `codegraph sync` in the background,
  or fall back to manual sync; either way the user is told the index
  stays frozen until re-synced. uninit removes the hooks.
- Tests: watch-policy + git-hooks (idempotency, user-content preservation,
  core.hooksPath).

Root-cause analysis and workaround by @mengfanbo123.

Closes #199

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-05-20 10:32:08 -05:00
committed by GitHub
co-authored by Claude Opus 4.7
parent 79b9601aae
commit cf7db7cb98
10 changed files with 714 additions and 5 deletions
+24
View File
@@ -20,6 +20,17 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
the line number while the line-numbered arm answered with zero follow-up
tool calls. Payload cost is small (~3-5%). Set
`CODEGRAPH_EXPLORE_LINENUMS=0` to disable.
- **MCP / watcher**: CodeGraph now skips the live file watcher on WSL2
`/mnt/*` drives, where recursive `fs.watch` is slow enough to break MCP
startup (see Fixed). When the watcher is off, `codegraph init` /
`codegraph install` offer to keep the index fresh via git hooks
(`post-commit`, `post-merge`, `post-checkout`) that run `codegraph sync`
in the background — accept for automatic refresh on commit / pull /
checkout, or decline and sync by hand. Either way you're told the index
stays frozen until it's re-synced. New controls: `CODEGRAPH_NO_WATCH=1`
(or `codegraph serve --mcp --no-watch`) forces the watcher off anywhere;
`CODEGRAPH_FORCE_WATCH=1` overrides the WSL auto-detect when your `/mnt`
setup is actually fast. `codegraph uninit` removes any hooks it installed.
### Changed
- **MCP / explore**: `codegraph_explore` output is now adaptive to project
@@ -46,6 +57,19 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Thanks to [@essopsp](https://github.com/essopsp) for the repro.
### Fixed
- **MCP**: the server no longer hangs on startup under WSL2 when the project
lives on an NTFS `/mnt/*` mount. Setting up the recursive file watcher
there took tens of seconds — every directory read crosses the Windows/9p
boundary — which blew past the host's initialization timeout (opencode's
30s), so the codegraph tools silently never appeared, even on small
projects. This is the file-watcher half of the
[#172](https://github.com/colbymchenry/codegraph/issues/172) startup fix:
that one moved the database/WASM open off the handshake, but the watcher
setup was still on the critical path. CodeGraph now auto-skips the watcher
on those mounts, with manual and git-hook sync fallbacks (see Added).
Closes [#199](https://github.com/colbymchenry/codegraph/issues/199).
Thanks to [@mengfanbo123](https://github.com/mengfanbo123) for the precise
root-cause analysis and workaround.
- **Installer (Claude Code)**: project-local installs (`Just this project`)
now write the MCP server to `.mcp.json` in the project root — the file
Claude Code actually reads for project-scoped servers. Previously they