Skip to content

fix(duckdb-watch): tombstone deletions on unwatched/deferred roots - #31

Merged
diazMelgarejo merged 2 commits into
cursor/agentsview-plus-periscope-f559from
cursor/duckdb-watch-unwatched-poller-c4ae
Jul 29, 2026
Merged

fix(duckdb-watch): tombstone deletions on unwatched/deferred roots#31
diazMelgarejo merged 2 commits into
cursor/agentsview-plus-periscope-f559from
cursor/duckdb-watch-unwatched-poller-c4ae

Conversation

@diazMelgarejo

@diazMelgarejo diazMelgarejo commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Bug and impact

Local periscope duckdb watch never tombstones deletions under unwatched or deferred watch roots.

When a session source lives under a root the file watcher cannot cover (symlinked recursive root, root missing at startup, coverage lost at runtime, or persistent polling dir), deleting the source file leaves the session active in SQLite and in the DuckDB mirror indefinitely. Interval floor pushes only run SyncAll, which does not prove missed deletions.

This is the same class of bug that PG watch fixed with unwatchedRootPoller + ReconcileWatchRoots; DuckDB watch was missing that owner.

Concrete trigger: Run periscope duckdb watch with a Codex root configured as a symlink to a real directory → sync a session → delete the JSONL → wait for the interval push → session remains in SQLite and DuckDB.

Not a PR #29 overlay regression: archive_write_backend.go is byte-identical to the purified base on the synthesis branch; the gap predates the merge overlay.

Root cause

PGPushWatch wires newArchivePushUnwatchedPoller, passes a shared sync Engine to the watcher, and reconciles deferred scopes via ReconcileWatchRoots on poll ticks. DuckDBPushWatch (local mode) passed nil engine, had no poller, and relied on runLocalSyncAuthoritative + interval pushes only.

Fix

Mirror PG watch for local DuckDB watch:

  • Long-lived sync Engine shared with watcher and poller
  • unwatchedRootPoller for deferred/unwatched roots
  • syncWatchBatch on watcher events
  • duckDBPusher that runs SyncAll then pushes the mirror (no per-push engine recreation)

Validation

  • TestLocalDuckDBPushWatchGivesDeferredScopesAPollingOwner (new) — mirrors existing PG test
  • TestLocalPGPushWatchGivesDeferredScopesAPollingOwner — still passes
  • TestPushWatchProductionOwners* — still passes
  • TestLocalArchiveWriteBackendDuckDBPushValidatesRemoteBeforeLocalSync — still passes

Stacks on #29 (cursor/agentsview-plus-periscope-f559).

Note: Daemon-delegated duckdb watch (CLI → daemon HTTP push) is unchanged; the daemon's main serve loop already owns unwatched polling for its watch path.

Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Improved local DuckDB watch synchronization by combining change detection, recovery, and authoritative polling.
    • Added support for automatically mirroring local updates into DuckDB during watch operations.
    • Improved startup synchronization and handling of interrupted operations.
  • Bug Fixes

    • Ensured changes in unwatched scopes become available after authoritative polling.
    • Improved clean shutdown behavior for local DuckDB watches.

Local duckdb watch omitted the probe-gated authoritative poller that pg
watch uses for deferred/unwatched roots. Deletions under those scopes were
never tombstoned in SQLite or the DuckDB mirror because interval pushes
only run SyncAll, which does not prove missed deletions.

Mirror pg watch: share one sync engine with the watcher, register polling
obligations for unwatched roots, and reconcile them via ReconcileWatchRoots.
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 453e072f-5af2-4f23-accb-86902f6e04c7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

DuckDB watch synchronization

Layer / File(s) Summary
DuckDB pusher and mirror flow
cmd/periscope/duckdb_watch.go
Adds a reusable pusher that performs local sync, mirrors data with DuckDB sync options, and completes watch push results.
Watch engine and polling ownership
cmd/periscope/archive_write_backend.go
Uses a long-lived sync engine, adds watcher recovery synchronization, assigns fallback obligations to the unwatched poller, and selects DuckDB startup and push hooks.
Deferred-scope polling validation
cmd/periscope/archive_write_backend_test.go
Verifies that unwatchable DuckDB watch scopes are reconciled after authoritative polling ticks and shut down cleanly.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DuckDBPushWatch
  participant syncpkg.Engine
  participant startArchivePushWatcher
  participant syncWatchBatch
  participant unwatchedPoller
  participant duckDBPusher

  DuckDBPushWatch->>syncpkg.Engine: create shared watch engine
  DuckDBPushWatch->>startArchivePushWatcher: start watcher
  startArchivePushWatcher->>syncWatchBatch: synchronize recovered watch scope
  syncWatchBatch-->>DuckDBPushWatch: notify push loop
  startArchivePushWatcher->>unwatchedPoller: register fallback obligation
  unwatchedPoller-->>DuckDBPushWatch: deliver authoritative poll tick
  DuckDBPushWatch->>duckDBPusher: push synchronized changes
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing DuckDB watch behavior for unwatched and deferred roots.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/duckdb-watch-unwatched-poller-c4ae

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

Comment @coderabbitai help to get the list of available commands.

@diazMelgarejo
diazMelgarejo marked this pull request as ready for review July 29, 2026 09:43
@cursor

cursor Bot commented Jul 29, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@diazMelgarejo

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
cmd/periscope/archive_write_backend.go (1)

831-836: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Nitpick: PG-named function used as the default DuckDB startup sync.

runPGWatchStartupSync is the fallback when neither duckDBStartupSync nor pgStartupSync hooks are set. This is intentional per the new test's comment (DuckDB mirrors PG's SyncAll-based startup), but the name may confuse future readers of the DuckDB path. Consider a small doc comment here explaining why the PG-named function is the shared default.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/periscope/archive_write_backend.go` around lines 831 - 836, Add a brief
comment at the startupSync initialization in the DuckDB startup path explaining
that runPGWatchStartupSync is intentionally the shared default because DuckDB
mirrors PG’s SyncAll-based startup behavior. Leave the hook precedence and
assignment logic unchanged.
cmd/periscope/archive_write_backend_test.go (1)

936-1055: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider deduplicating with TestLocalPGPushWatchGivesDeferredScopesAPollingOwner.

This test (Lines 938-1055) duplicates almost the entire body of the existing PG variant (Lines 821-934), differing only in hook field names/types and a couple of literals. A shared helper parameterized over the push-watch entry point, hook wiring, and result type would cut duplication and reduce drift risk if the scenario's timing/assertions change later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/periscope/archive_write_backend_test.go` around lines 936 - 1055,
Deduplicate TestLocalDuckDBPushWatchGivesDeferredScopesAPollingOwner with
TestLocalPGPushWatchGivesDeferredScopesAPollingOwner by extracting their shared
setup, polling-obligation assertion, file creation, reconciliation check, and
shutdown flow into a parameterized helper. Pass the backend-specific push-watch
entry point, hook wiring, and result/type differences through helper parameters,
while keeping each test as a thin wrapper that supplies its variant-specific
values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cmd/periscope/archive_write_backend_test.go`:
- Around line 936-1055: Deduplicate
TestLocalDuckDBPushWatchGivesDeferredScopesAPollingOwner with
TestLocalPGPushWatchGivesDeferredScopesAPollingOwner by extracting their shared
setup, polling-obligation assertion, file creation, reconciliation check, and
shutdown flow into a parameterized helper. Pass the backend-specific push-watch
entry point, hook wiring, and result/type differences through helper parameters,
while keeping each test as a thin wrapper that supplies its variant-specific
values.

In `@cmd/periscope/archive_write_backend.go`:
- Around line 831-836: Add a brief comment at the startupSync initialization in
the DuckDB startup path explaining that runPGWatchStartupSync is intentionally
the shared default because DuckDB mirrors PG’s SyncAll-based startup behavior.
Leave the hook precedence and assignment logic unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 24125667-9b16-4949-88d9-07b477b2a4d4

📥 Commits

Reviewing files that changed from the base of the PR and between 45dac3d and 56bf081.

📒 Files selected for processing (3)
  • cmd/periscope/archive_write_backend.go
  • cmd/periscope/archive_write_backend_test.go
  • cmd/periscope/duckdb_watch.go

@diazMelgarejo
diazMelgarejo merged commit e6fa88c into cursor/agentsview-plus-periscope-f559 Jul 29, 2026
12 of 13 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