Skip to content

fix(code-index): restore watcher events after restart - #1835

Open
WebMad wants to merge 3 commits into
Zoo-Code-Org:mainfrom
WebMad:fix/code-index-watcher-restart
Open

WebMad wants to merge 3 commits into
Zoo-Code-Org:mainfrom
WebMad:fix/code-index-watcher-restart

Conversation

@WebMad

@WebMad WebMad commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Refs #1819; second narrow split from #1821, independent of #1834 and based directly on main.

  • Restore batch emitters after disposal and reinitialization.
  • Avoid duplicate native watchers and reset watcher/timer references on disposal.
  • Capture per-session emitters so old batches cannot notify subscribers of a restarted session.
  • Include concrete watcher tests and accurate disposed-emitter mocks; existing test ESLint suppressions decrease from 25 to 22.

Scope decision / rollback

At the author's request, commit bae7be8 reverts the additional active-batch draining work from 770f0eb. The watcher implementation matches #1821 at 27fdc41. The idle/drain API and follow-up tests were removed. Original PR #1835 test improvements remain.

This PR isolates notifications only. It does not await or cancel active persistence writes before restart or index clearing. The Persistence Integrity finding about overlapping old/new writes remains UNRESOLVED. This rollback is a scope decision, not a persistence-safety fix. No additional regression fixes or orchestrator changes are included.

Validation after rollback

  • Complete code-index suite: 685 tests passed across 30 files.
  • Changed-file ESLint with suppression pruning and TypeScript no-emit checks passed.
  • Commit/push lint and type hooks and diff whitespace checks passed.
  • Local Node 24.7.0 differs from requested 22.23.1; CI still needs verification.

@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Summary

Summary by CodeRabbit

  • Bug Fixes
    • Prevented stale file-processing events from appearing after a watcher restart.
    • Improved recovery after reinitialization and prevented duplicate file watchers.
    • Ensured pending work and timers are cleared on disposal.
    • Improved error reporting while preserving cached file hashes when deletion fails.

FileWatcher now recreates disposed batch event emitters during initialization and clears watcher and debounce state during disposal. Each batch uses the progress and completion emitters captured at its start. Tests cover restart isolation, repeated initialization, disposal, and deletion failure.

Changes: FileWatcher lifecycle and batch events

Layer / File(s) Summary
Emitter and watcher lifecycle
src/services/code-index/processors/file-watcher.ts, src/services/code-index/processors/__tests__/file-watcher.spec.ts
Getters expose the current batch event emitters. Initialization recreates disposed emitters. Disposal resets watcher and timer state. Tests cover reinitialization, duplicate initialization, and disposal behavior.
Batch-scoped event delivery
src/services/code-index/processors/file-watcher.ts, src/services/code-index/processors/__tests__/file-watcher.spec.ts, src/eslint-suppressions.json
Batch processing passes captured emitters through progress and completion notifications. Tests cover restart isolation and deletion failure outcomes. The test file’s no-explicit-any suppression count decreases from 25 to 22.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant FileWatcher
  participant BatchListeners
  participant processBatch
  FileWatcher->>BatchListeners: Fire batch start event
  FileWatcher->>processBatch: Pass captured progress and completion emitters
  processBatch->>BatchListeners: Deliver progress and completion events through captured emitters
Loading
Merge Risk: Minimal · up to 16e5b

Watcher notifications are intended to resume after restart without sending older batch events to new subscribers. No actionable merge-blocking issue is established; complete the normal CI checks before merging.

Security architecture risk: Low · up to 16e5b

The restart change keeps notifications from an older batch separate from new subscribers. It does not appear to widen access to indexed files or add a new destination for their data. Already-running indexing work can still finish after disposal; that limitation predates this change.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — Filesystem events can affect the existing workspace index and its vector-store and cache writes. The changed notification routing does not establish a new file-access path or sensitive sink.

Trust Boundaries and Controls

  • observed — Emitter capture occurs before start listeners run; subsequent progress and completion notifications use those captured emitters. Disposal removes the old listeners, while the orchestrator releases its old subscriptions.

Resilience and Maintainability Implications

  • inferred — An old batch can still write after watcher disposal or alongside a new batch. The inspected change leaves that pre-existing index-state limitation in place rather than introducing cancellation or write-session isolation.

Hardening Proposals

  • proposed — If clearing or replacing an index must exclude late writes, drain active batches or enforce write-session ownership before clearing shared index state. This is a separate safeguard, not a protection supplied by the notification change.

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

Failed Failed checks: 1 warning · 1 inconclusive

Check name Status Explanation Resolution
Description check Warning The description covers the implementation (emitter restoration, duplicate native watcher avoidance, per-session emitter capture), the scope decision and rollback, and validation results. It states the… Add a "Closes: #" line under "Related GitHub Issue" (the template requires a link to an approved issue; the description currently cites "Refs #1819"). Add a "Test Procedure" section with steps reviewers can repeat, such as the exact …
Linked Issues check Inconclusive The description references issue #1819 and related pull requests, but the context does not confirm that #1819 is an approved issue or that the required issue-link format is satisfied. Confirm that issue #1819 is approved and update the description to use the required Related GitHub Issue format, such as Closes: #1819, if applicable.
Passed Passed checks: 6 passed

Check name Status Explanation
Out of Scope Changes check Passed The description clearly limits the change to watcher notifications and states that persistence draining, cancellation, and orchestrator changes remain out of scope. This matches the stated objectives.
Title check Passed The title "fix(code-index): restore watcher events after restart" matches the main change. The file-watcher.ts changes recreate disposed batch event emitters on reinitialization, and the new tests cov…
Regression Evidence Passed Changed behavior in src/services/code-index/processors/file-watcher.ts, verified from the reviewed diff: 1. Emitters are recreated on initialize() after dispose(), and dispose() clears `fileWa…
Security Boundaries Passed The PR changes three files: src/eslint-suppressions.json (one suppression count, 25 to 22), src/services/code-index/processors/__tests__/file-watcher.spec.ts (test mocks and tests), and `src/servi…
Persistence Integrity Passed The PR changes three files: src/services/code-index/processors/file-watcher.ts, its spec, and src/eslint-suppressions.json. The only production change is in file-watcher.ts, and it is limited to…
Lifecycle Resource Cleanup Passed The diff changes three files: src/eslint-suppressions.json, file-watcher.spec.ts, and src/services/code-index/processors/file-watcher.ts. I read the full changed file-watcher.ts lifecycle path…
Full details: Title check

Explanation

The title "fix(code-index): restore watcher events after restart" matches the main change. The file-watcher.ts changes recreate disposed batch event emitters on reinitialization, and the new tests cover event delivery after restart. It is short, specific, and in active voice. It uses the conventional commit form, which fits a repository history scan.

Full details: Regression Evidence

Explanation

Changed behavior in src/services/code-index/processors/file-watcher.ts, verified from the reviewed diff: 1. Emitters are recreated on initialize() after dispose(), and dispose() clears fileWatcher and the debounce timer. 2. initialize() returns early when a native watcher already exists. 3. Each batch captures its session's progress and finished emitters and passes them through the deletion, upsert and finalize phases. Coverage in src/services/code-index/processors/__tests__/file-watcher.spec.ts, a unit-level spec at the lowest valid layer for this class: - "does not deliver an old batch into subscriptions created after restart" covers the per-session emitter capture. It blocks an in-flight batch, disposes and reinitializes, and asserts the old batch's progress and finished events are not delivered. It then asserts a new batch still delivers events. - "restores all batch events after disposal and reinitialization" covers emitter recreation and checks that the old listener is not called. - "does not create duplicate native watchers on repeated initialization" covers the early-return guard. - "discards queued events and the pending timer when disposed before a batch starts" covers timer and queue reset and the double-dispose path. - "reports progress and preserves cached hashes when batch deletion fails" covers the error branch of the deletion phase, including progress sequence, finished payload, and no hash updates or upserts. The mock emitter changes make disposed emitters drop listeners and events. This matches the real behavior that the new tests depend on. The ESLint suppression count change is mechanical. Gaps I noticed but judged non-blocking under the check's criteria. The upsert-phase error branch was not newly covered. Its behavior is unchanged apart from which emitter it fires on, and the stale-session test exercises the upsert path via processFile. The check does not require every branch without a plausible regression scenario. No changed behavior lacks focused coverage at the unit layer.

Full details: Security Boundaries

Explanation

The PR changes three files: src/eslint-suppressions.json (one suppression count, 25 to 22), src/services/code-index/processors/__tests__/file-watcher.spec.ts (test mocks and tests), and src/services/code-index/processors/file-watcher.ts. The file-watcher.ts diff covers event-emitter lifecycle only. It converts three public event properties to getters, recreates emitters on initialize() after disposal, clears the watcher and debounce timer on dispose(), and passes per-session emitters through processBatch and its helpers. I checked each failure condition against this diff: - Secrets or PII leak: No changed path logs, returns, or emits credentials or personal data. The emitters carry file paths, progress counts, and batch summaries, which the existing code already emitted. - Unvalidated input trusted or executed: No changed path adds or removes input parsing, path validation, or command execution. The watcher still processes the same file events through the same existing code. - Approval or allowlist bypass: No changed path touches approval, allowlist, or permission logic. The changes affect event delivery and lifecycle only, so the check passes.

Full details: Persistence Integrity

Explanation

The PR changes three files: src/services/code-index/processors/file-watcher.ts, its spec, and src/eslint-suppressions.json. The only production change is in file-watcher.ts, and it is limited to event routing and watcher lifecycle: - Batch events are exposed through getters. Emitters are recreated in initialize() after disposal. - initialize() returns early when a watcher already exists. - dispose() clears fileWatcher and the debounce timer, and marks emitters disposed. - triggerBatchProcessing() captures the session's progress and finished emitters and passes them into processBatch(). The persistence code is unchanged in the diff. The vector delete and upsert calls, the cacheManager hash updates, and their await ordering are not modified. orchestrator.ts is also unchanged. stopWatcher() still calls fileWatcher.dispose() without awaiting an active batch, and clearIndexData() still runs as before. On the prior FAIL: the overlap it describes is real. dispose() does not track or await processBatch(), so an in-flight batch can still write after restart. That behavior predates this PR. In the base, initialize() had no guard and re-created the watcher after dispose(). The base dispose() also did not track or await active batches. The PR does not add the overlap path, and it does not change write ordering or awaits. The prior claim that the base "did not permit reinitializing" the watcher is factually wrong. The PR's own description states that the drain work was removed by scope decision and that the persistence-overlap finding remains open. That is a real gap, but it is not a changed-code defect under this check's criteria. The check requires a changed persistence path that loses or corrupts state through an unawaited operation, a non-atomic write, missing rollback, or a missing default. The diff does not add one. Prior failure refuted: The overlap of an in-flight processBatch with a restarted watcher predates this PR. Base dispose() did not track or await batches, and base initialize() had no guard, so it re-created the watcher after dispose. The diff (only file-watcher.ts changes in production code) leaves processBatch's vector delete/upsert and cacheManager writes and their await ordering unchanged. orchestrator.ts is uncha...

Full details: Lifecycle Resource Cleanup

Explanation

The diff changes three files: src/eslint-suppressions.json, file-watcher.spec.ts, and src/services/code-index/processors/file-watcher.ts. I read the full changed file-watcher.ts lifecycle paths. Changed lifecycle paths and their cleanup behavior: - initialize() now returns early when this.fileWatcher already exists (line 116). This prevents a second native FileSystemWatcher from being created without disposing the first. This removes a leak path. - initialize() recreates the three emitters only when eventsDisposed is true (lines 117-126). Disposed emitters are replaced before a restart. - dispose() now disposes fileWatcher and sets it to undefined (lines 144-145). It clears batchProcessDebounceTimer and sets it to undefined (lines 146-149). It also marks eventsDisposed = true (line 153). Native watcher, timer, and emitter references are released. - triggerBatchProcessing() captures the current emitters in batchEvents (lines 205-214). Progress and finish events from an older batch go to its own captured emitters. This prevents stale batch events from reaching a restarted session. Residual behavior: dispose() does not cancel an in-flight processBatch. The old batch can still run its persistence work after restart. The author states this explicitly in the PR description. This behavior predates the change. The diff only changes notification routing and does not add new work, timers, or watchers. The debounce timer and native watcher are both cleared on disposal, and no new listener or timer is created on any changed path. No changed path leaks a listener, watcher, provider, or timer. No changed path duplicates work because of cancellation, disposal, or restart beyond the pre-existing in-flight batch behavior. The check passes.

Full details: Description check

Explanation

The description covers the implementation (emitter restoration, duplicate native watcher avoidance, per-session emitter capture), the scope decision and rollback, and validation results. It states the scope limits clearly. It does not follow the repository template exactly: it has no "Related GitHub Issue" section with a Closes: line (it uses "Refs #1819" instead, and the template requires an approved issue link), no "Test Procedure" section with reviewer repeat steps, no Pre-Submission Checklist, no Documentation Updates section, no Get in Touch section, and no Visual Snapshots or Videos handling. The description is mostly complete on content but misses several template sections.

Resolution

Add a "Closes: #<issue>" line under "Related GitHub Issue" (the template requires a link to an approved issue; the description currently cites "Refs #1819"). Add a "Test Procedure" section with steps reviewers can repeat, such as the exact test command for the code-index suite. Add the "Pre-Submission Checklist" with each item marked. Add a "Documentation Updates" entry that marks whether docs need changes. Add the "Get in Touch" section with the author's Discord username. Mark "Visual Snapshots" and "Videos" as not applicable, since this PR has no UI change.

Finishing Touches

🧪 Generate unit tests (beta)

  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

ℹ️ Recent review info

⚙️ Run configuration

Configuration used: Repository: Zoo-Code-Org/Zoo-Code/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 99bd833b-d56d-4918-8714-51ed460aba8c

📥 Commits

Reviewing files that changed from the base of the PR and between d351a15 and 16e5bcd.

📒 Files selected for processing (3)

  • src/eslint-suppressions.json
  • src/services/code-index/processors/__tests__/file-watcher.spec.ts
  • src/services/code-index/processors/file-watcher.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.

📜 Recent review details

🧰 Additional context used

📓 Path-based instructions (5)

Check persistence and lifecycle invariants: awaited atomic writes, rollback or explicit partial-failure behavior, cross-window state consistency, stale listeners/watchers, cancellation, idempotency, and safe restart/resume without lost or d...

CodeRabbit configuration file

Files:

  • src/services/code-index/processors/__tests__/file-watcher.spec.ts
  • src/services/code-index/processors/file-watcher.ts
Require regression coverage at the lowest valid harness with behavior-focused assertions, including relevant negative, error, false/unset, and boundary cases.

CodeRabbit configuration file

Files:

  • src/services/code-index/processors/__tests__/file-watcher.spec.ts
Check strict typing and exhaustive behavior across normal, boundary, error, cancellation, retry, and compatibility paths.

CodeRabbit configuration file

Files:

  • src/services/code-index/processors/__tests__/file-watcher.spec.ts
  • src/services/code-index/processors/file-watcher.ts
Verify extension/webview contracts, cancellation and error propagation, VS Code lifecycle correctness, and behavior under retries and partial failure.

CodeRabbit configuration file

Files:

  • src/eslint-suppressions.json
  • src/services/code-index/processors/__tests__/file-watcher.spec.ts
  • src/services/code-index/processors/file-watcher.ts
Act as an adversarial second-opinion reviewer.

CodeRabbit configuration file

Files:

  • src/eslint-suppressions.json
  • src/services/code-index/processors/__tests__/file-watcher.spec.ts
  • src/services/code-index/processors/file-watcher.ts
🔇 Additional comments (3)

src/services/code-index/processors/file-watcher.ts (1)

44-72: LGTM!

Also applies to: 116-126, 145-153, 205-214, 227-227, 246-246, 269-269, 287-287, 301-301, 367-367, 452-463, 492-492, 508-508, 521-531

src/services/code-index/processors/__tests__/file-watcher.spec.ts (1)

41-59: LGTM!

Also applies to: 94-126, 128-148, 150-155, 157-172, 174-204

src/eslint-suppressions.json (1)

1399-1399: LGTM!

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Review status

Thanks for contributing. This comment tracks the review sequence and the next action.

Current step: Required CI passed. Waiting for automated review of the latest commit.

If automated review does not start, a maintainer must restart it.

Review-state labels are managed by this workflow; do not edit them manually. community-approved is managed the same way — do not add or remove it manually. It signals a fresh community code approval for the current head as an advisory priority only; maintainer review is still required.

@codecov

codecov Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions github-actions Bot added coderabbit-review-active Required CI passed; CodeRabbit review is active awaiting-coderabbit Waiting for CodeRabbit to approve the latest commit labels Sep 28, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-merge checks failed. Please resolve the failing checks before merging.

@github-actions github-actions Bot added awaiting-author PR is waiting for the author to address requested changes and removed coderabbit-review-active Required CI passed; CodeRabbit review is active awaiting-coderabbit Waiting for CodeRabbit to approve the latest commit labels Sep 28, 2026
@github-actions github-actions Bot removed the awaiting-author PR is waiting for the author to address requested changes label Sep 28, 2026
@github-actions github-actions Bot added coderabbit-review-active Required CI passed; CodeRabbit review is active awaiting-coderabbit Waiting for CodeRabbit to approve the latest commit labels Sep 28, 2026
@github-actions github-actions Bot added coderabbit-review-active Required CI passed; CodeRabbit review is active and removed coderabbit-review-active Required CI passed; CodeRabbit review is active labels Nov 9, 2026

This branch has not been deployed

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

Labels

awaiting-coderabbit Waiting for CodeRabbit to approve the latest commit coderabbit-review-active Required CI passed; CodeRabbit review is active

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant