fix: watcher leak, registry write races, and stalled scans - #28
Merged
Conversation
A tab's watch() call could resolve after its path changed, at which point the id-based guard still passed (the id is wanted and the map holds a slot) and the real unwatch was assigned to an orphaned slot, leaking an FSEvents stream for the rest of the session. Compare slot identity instead: if the map no longer holds the exact slot this attach created, unwatch immediately. That covers path swaps, tab close, unmount, and rapid successive swaps with one check. The rejection path now deletes only its own slot, so a failed stale attach cannot evict the live watcher that replaced it. Fixes #25
Concurrent MCP sidecars each read the registry, added their entry, and wrote the whole file back, so simultaneous registrations silently dropped each other. Measured with 48 concurrent sidecar processes: 41-45 of 48 workspaces survived before, 48 of 48 after. Hold an exclusive advisory lock across the load-modify-save sequence, and replace the in-place write with a temp file plus rename so a reader never observes a partial file. The lock lives on a sidecar path because the rename swaps the registry inode, which would leave writers holding locks on different inodes and never contending. Fixes #26
A scan on unreachable storage never settles, leaving the workspace spinner running indefinitely. Fail it when no progress arrives for 60s rather than on a fixed deadline, so a large but healthy scan is never cut off: a 55k-file workspace scans in ~3s with at most 122ms between progress events, leaving a wide margin. This is the mitigation half of #27. The upstream defect it guards against (tauri-apps/tauri#12338) is still open, so #27 stays open to track that fix landing in our Tauri version. Refs #27
This was referenced Jul 18, 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.
Fixes the three code-analysis findings filed after the v0.9.1 sync work. Each was proven with a test that fails against the current code, and the registry fix was additionally measured end to end with real concurrent sidecar processes.
Changes
Watcher leak (
useTabs) - a tab'swatch()could resolve after its path changed. The old id-based guard still passed, so the realunwatchwas assigned to an orphaned slot and the FSEvents stream leaked for the session. Now the resolved attach checks whether the map still holds the exact slot it created, which covers path swaps, tab close, unmount, and rapid successive swaps in one comparison. The rejection path no longer evicts the live watcher that replaced it.Registry atomicity (core) - concurrent MCP sidecars each read the registry, added an entry, and wrote the whole file back, silently dropping each other's workspaces. Now an exclusive advisory lock spans the load-modify-save, and writes go through a temp file plus rename. The lock is on a sidecar path deliberately: the rename swaps the registry inode, so a lock on the registry itself would leave writers holding different inodes and never contending.
Stalled scans (
scan) - a scan on unreachable storage never settles, pinning the workspace spinner. It now fails after 60s without progress, an inactivity window rather than a fixed deadline so healthy large scans are never cut off.Evidence
Registry, real concurrent sidecar processes (not threads):
Zero leftover temp files across all fixed runs.
Scanner load test on synthetic workspaces:
truncated=true, ~1.9-3.3s, max gap 122ms. The 60s inactivity window therefore has roughly a 490x margin over the worst observed healthy gap.skipped, all real files still found, and the symlink loop did not hang the walk.Flakiness: registry concurrency test 12/12, full frontend suite 5/5.
Suite: 135 frontend tests, 81 core tests,
tsc --noEmitclean,cargo clippy --workspace --all-targetsclean.Fixes #25
Fixes #26
Refs #27