Skip to content

fix(lsp): rescan when a watched directory is replaced while its watch attaches - #268

Merged
mikn merged 1 commit into
mainfrom
mikn/hook-worker-attach-recheck
Oct 9, 2026
Merged

mikn merged 1 commit into
mainfrom
mikn/hook-worker-attach-recheck

Conversation

@mikn

@mikn mikn commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator

On macOS the tsserver hook worker can miss a directory replaced while its watch attaches, so an editor keeps a stale resolution map and //tests/lsp:test_worker_map hangs until its 60 s timeout, failing macOS CI.

Problem

The worker learns that a watched ancestor was replaced only from the parent watcher's event. On macOS a directory fs.watch is an FSEvents stream that starts asynchronously, and a change made while it starts sends no event. Under load, that window covers the test's replacement, so the worker never rebuilds. Linux inotify is live when fs.watch returns, which is why Linux passes.

Observed in production

Not observed in production; the worker is editor tooling. It failed rules_typescript#267's macOS unit job twice, on a runner busy compiling oj from scratch. An editor on a loaded Mac can hit the same gap.

Gain

The worker no longer depends on an event FSEvents may drop, so replacements reach the map on every platform, and #267's macOS job stops timing out.

What changes

  • After attaching a watch, the worker re-stats the directory and schedules a rebuild when its dev/inode changed during the attach.
  • rebuildTimer is declared before the initial build, which can now schedule a rebuild during its own watch attachment. Previously that call hit the uninitialized let and was swallowed by watchDirectory's error handler.
  • worker_map_test.mjs drops the parent's event for the replaced directory, as FSEvents can, so the test fails without the re-check on Linux too.

🤖 Generated with Claude Code

… attaches

The tsserver hook worker learned of a replaced ancestor directory only from
the parent watcher's event. On macOS a directory fs.watch is an FSEvents
stream that starts asynchronously, so a replacement made while the watch
attaches can send no event; on a loaded runner //tests/lsp:test_worker_map
then waited out its 60 s timeout. After attaching a watch, the worker now
re-stats the directory and schedules a rebuild when its identity changed.
rebuildTimer moves above the initial build, which can now schedule a rebuild
during its own watch attachment.

The test drops the parent's event for the replaced directory, as FSEvents
can, so it fails without the re-check on every platform.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mikn
mikn merged commit c9c4467 into main Oct 9, 2026
21 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant