Repository navigation
fix(server): rescan the workspace index once its file watcher goes live - #7
Francois3d wants to merge 1 commit into
Conversation
`WorkspaceSearchIndex.make` treated `waitForIndexReady` as meaning the index was live, but that call only covers the scan and warmup phases — the background filesystem watcher goes live up to a second later. Every external change in that window was lost permanently, because nothing rescans once the watcher does start. It bit hardest right after a workspace was first opened, which is exactly when a user is most likely to be creating files. Waiting for the watcher before serving would put that second in front of the file tree on every workspace open, so the index still serves as soon as the scan finishes and the window is closed from behind instead: a scoped background fiber waits for the watcher off the critical path, then rescans once to pick up whatever the window swallowed. Workspaces whose watcher is already live when the scan ends skip the rescan, so the extra scan is only paid where a blind window actually existed. A watcher that never becomes ready, or whose readiness cannot be read at all, degrades to a non-live index with a warning rather than taking the index down: search still answers correctly, it just stops tracking edits until the next refresh. Closes #5 Model: Claude Opus 5 (claude-opus-5) via Claude Code
|
Closing as redundant — this change is already on
The stalled CI here was a separate issue: this branch was cut from
|
Problem
WorkspaceSearchIndex.maketreatedfinder.waitForIndexReady()as meaning the index was fully live. It is not — that call only covers the scan and warmup phases, while the background filesystem watcher goes live up to a second later:Any external filesystem change in that window was missed permanently: the watcher was not listening, and nothing triggered a rescan afterwards. It bit hardest right after a workspace was first opened, which is exactly when a user is most likely to be creating files.
Fix
Waiting for the watcher before serving would put that second in front of the file tree on every workspace open, so the index still serves as soon as the scan finishes and the window is closed from behind instead. A scoped background fiber waits for the watcher off the critical path, then rescans once to pick up whatever the window swallowed.
Effect.forkScopedties the fiber to the index's scope, so LayerMap's idle eviction and shutdown interrupt it before the finder is destroyed.Polling is what the native index itself offers —
waitForIndexReadypolls the same snapshot — so there is no watcher-ready event to wait on instead.Evidence
Measured on a 20k-file tree, creating a file externally immediately after the index is constructed:
makeresolveslistEntriesThe blocking column is the obvious fix, included to show why it was rejected: it buys the same correctness at the cost of putting watcher latency in front of every workspace open.
Tests
A real-finder regression test builds a 20k-file tree, constructs the index, creates a file externally and asserts it reaches
listEntrieswith norefresh(). It fails on pre-fix code and passes here, repeatably — a smaller tree does not separate scan-complete from watcher-ready and stops guarding the window at all.Four
TestClocktests cover the rescan firing, the already-live skip, the never-ready degrade including its warning, and the unreadable-watcher short-circuit.vp test run apps/server/src/workspace→ 69 passed. Typecheck, lint and format clean for the touched scope.Closes #5
Model: Claude Opus 5 (claude-opus-5) via Claude Code