fix(watcher): stop cross-worktree fan-out via shared commondir#2971
Merged
pauldambra merged 2 commits intoJul 1, 2026
Merged
Conversation
Linked worktrees share the main repo's `.git` as their `commondir`, and each worktree's `watchRepo` recursively watches that commondir. So mutating any worktree's admin dir under `.git/worktrees/` — notably creating a new worktree — woke every other worktree's watcher, each firing a server-side branch re-check (a `git` subprocess) and a renderer branch-query invalidation. The per-event cost grew linearly with the number of worktrees, which is why starting a new task got slower as worktrees accumulated. Exclude the `worktrees/` admin subtree from the git-dir watches. A worktree's own admin dir is watched directly as its `gitDir` (rooted inside `worktrees/<name>`, where the pattern matches nothing), so its own HEAD changes are still observed, and shared refs (`refs/heads`, `packed-refs`) live outside `worktrees/` and are still observed too. Only cross-worktree noise is dropped. Generated-By: PostHog Code Task-Id: 94283e5b-6e63-4527-8b44-d51fb3fa8702
|
React Doctor found no issues in the changed files. 🎉 Reviewed by React Doctor for commit |
Contributor
|
Reviews (1): Last reviewed commit: "fix(watcher): stop cross-worktree fan-ou..." | Re-trigger Greptile |
Reflow the collectWatchCalls signature to satisfy `biome ci` (the quality check). Generated-By: PostHog Code Task-Id: 94283e5b-6e63-4527-8b44-d51fb3fa8702
Contributor
|
Reviews (2): Last reviewed commit: "style(watcher): apply biome formatting t..." | Re-trigger Greptile |
jonathanlab
approved these changes
Jul 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Starting a new worktree task gets slower as the number of existing worktrees grows. Reported from a fresh machine (few worktrees) feeling dramatically faster than an older one with many worktrees.
Root cause: linked worktrees share the main repo's
.gitas theircommondir, and each worktree'swatchReporecursively watches that commondir. So mutating any worktree's admin dir under.git/worktrees/— notably creating a new worktree (which writes.git/worktrees/<name>/HEAD) — woke every other worktree's watcher. Each wake fired a server-side branch re-check (agit rev-parsesubprocess) and a renderer branch-query invalidation. With N worktrees, one git operation triggered ~N subprocesses + N invalidations, so the per-event cost grew linearly with worktree count — and creating a task is exactly the moment that writes to the shared.git.Changes
Exclude the
worktrees/admin subtree (**/worktrees/**) from the git-dir watches inWatcherService.watchRepo.gitDir(rooted insideworktrees/<name>, where the pattern matches nothing), so its own HEAD changes are still observed.refs/heads,packed-refs) live outsideworktrees/, so commits/branch changes still propagate to sibling worktrees as before.Note: the
ignore-option mechanism is load-bearing here — it's relative to each watch root, so it suppresses the subtree only for the shared commondir watch and not for a worktree's own gitDir watch. Filtering by an absolute-path substring instead would wrongly drop a worktree's own HEAD events.How did you test this?
service.test.tscovering the ignore wiring for both a linked worktree (commondir + own gitDir get the worktrees ignore; working tree keeps node_modules/.git ignores) and a non-worktree repo. Both pass viavitest run src/services/watcher/service.test.ts.biome lintclean on the watcher dir;tscclean for the changed files (remaining repo typecheck errors are unbuilt sibling-packagedist/, unrelated to this change).Automatic notifications
Created with PostHog from a Slack thread