Skip to content

Workspace index serves before its file watcher is ready, losing ~1s of external changes #5

Description

@Francois3d

Problem

WorkspaceSearchIndex.make treats finder.waitForIndexReady() as meaning the index is fully live. It is not: the background filesystem watcher is still starting at that point. Measured on this repo:

waitForIndexReady returned at   52ms; isWatcherReady=false
isWatcherReady became true at 1094ms   => ~1.0s blind window

ScanProgress exposes isWatcherReady separately from isScanning and isWarmupComplete, and waitForIndexReady only polls the latter two.

Impact

Any external filesystem change in that ~1s window after an index is created is missed permanently — the watcher was not listening, and nothing triggers a rescan afterwards. The entry list silently under-reports until something forces scanFiles().

This is most likely to bite exactly when it is most visible: right after a workspace is first opened, which is when a user is most likely to be creating files.

Found while scoping #2 — the first probe run hit this and produced a false negative (an externally created file never appearing) before the cause was identified.

Sketch

In WorkspaceSearchIndex.make (apps/server/src/workspace/WorkspaceSearchIndex.ts), extend the readiness wait to also require getScanProgress().isWatcherReady, within the existing 15s budget and reusing the existing WorkspaceSearchIndexScanTimedOut failure. Decide deliberately whether a watcher that never becomes ready should fail index creation or degrade to serving a non-live index — degrading is probably right, since search still works, but it should be a choice rather than an accident.

Acceptance criteria

  • A file created externally immediately after the index is constructed appears in listEntries without an explicit refresh.
  • Index creation still completes within the existing 15s timeout on a large repo.

Conventions

Read docs/internals/code-conventions.md and .macroscope/check-run-agents/effect-service-conventions.md first.

Activity

  1. added
    bugSomething isn't working
    ready-for-agentFully specified, ready for an AFK agent
    on Aug 29, 2026
  2. Francois3d commented on Aug 29, 2026

    @Francois3d
    OwnerAuthor

    Fixed on local (the fork's default branch) in 174322f.

    Closing manually: the commit carried Closes #5, but it reached local while main was still the default branch, so GitHub never auto-closed this. PR #7 also referenced the issue but was closed rather than merged — the fix was already on local by then, so merging it would only have cost main its pristine-upstream-mirror status.

    What shipped. Not the blocking wait the sketch suggested. Waiting for the watcher before serving would have put ~1s in front of the file tree on every workspace open (measured: 54ms → 260ms on a 20k-file tree), 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.

    pre-fix blocking shipped
    make resolves 54ms 260ms 54ms
    external file reaches listEntries ✗ ✓ ✓

    On the two acceptance criteria. Both met. A real-finder test builds a 20k-file tree, constructs the index, creates a file externally and asserts it reaches listEntries with no refresh() — it fails on pre-fix code and passes now, repeatably. A smaller tree does not separate scan-complete from watcher-ready and stops guarding the window at all. Creation no longer blocks on the watcher, so the 15s budget is untouched.

    Deliberate divergence from the sketch. WorkspaceSearchIndexScanTimedOut is not reused. The sketch asked for it while also saying degrading is probably right, and those pull apart — degrading means never failing. A watcher that never becomes ready, or whose readiness cannot be read at all, now warns and leaves a non-live index serving: search still answers correctly, it just stops tracking edits until the next refresh. Reopen if you wanted the failure path instead.

    Verification: vp test run apps/server/src/workspace → 69 passed; typecheck, lint, format clean for the touched scope. CI has not run this change — the fork's runner situation is a separate thread.

    Model: Claude Opus 5 (claude-opus-5) via Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions