Skip to content

fix(worktrees): bound ownership refreshes and lazily cache inventories - #708

Merged
pascalandr merged 4 commits into
devfrom
fix/event-routing-refresh-storm
Sep 18, 2026
Merged

pascalandr merged 4 commits into
devfrom
fix/event-routing-refresh-storm

Conversation

@pascalandr

@pascalandr pascalandr commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Bound miss-triggered ownership refreshes and retain completed worktree inventories with demand-driven stale-while-revalidate reads for display consumers.

The shared native event bridge awaits directory ownership sequentially. Previously, each new foreign directory discarded the inventory and its negative cache, triggering another complete native/Git scan. Twenty foreign directories caused 21 inventory loads; the ownership regression now requires two and covers explicit invalidation and expiry.

The manager also used to retain only an in-flight inventory promise. Sequential UI reads therefore repeated the complete scan. WorktreeInventory now retains the last successful snapshot:

  • Display reads reuse it immediately. Expiry (10 seconds) or invalidation only schedules one background refresh when a consumer next reads it; there is no polling timer.
  • Concurrent callers share the scan, failures retain display data with demand-driven retry backoff, and invalidation fences obsolete results with serialized trailing validation.
  • Directory authorization uses validated reads, which wait if the snapshot is stale. Family create/move/delete checks force a fresh scan. Workspace disposal fences pending results.
  • A changed completed snapshot publishes workspace.worktreesChanged; existing UI consumers queue a reload behind older HTTP responses. Unchanged scans do not create notification feedback loops.
  • The selector opens before the reload completes. Duplicate selection notifications during inventory reconciliation no longer dismiss an open menu or produce a family move, and a delayed response cannot reopen a dismissed menu.

Measurements and scope

  • Before the initial miss-refresh correction, the installed renderer lagged native events by 331-336 seconds, versus 42-84 ms on a direct read-only native subscription.
  • The first post-restart sample caught up to 340-699 ms, but a subsequent 45-second sample still showed session median 851 ms, p95 12.2 seconds, and overall spikes of 15.5 seconds. The corresponding direct native session median was 52 ms and p95 339 ms. This is not a complete startup/event-latency fix.
  • A real 165-worktree inventory measured location.get 22 ms, native refresh 282 ms, and native list 5 ms, versus 24,067 ms for the whole CodeNomad inventory. The local validation/annotation pass launches three Git commands per inspected checkout.
  • This memory cache removes repeated display scans and lets warm display reads continue while stale data refreshes. It does not persist across backend restarts or reduce the cost of a cold or required authoritative scan. The serial event bridge can still await such validation.

Validation

  • 110 targeted server tests passed: inventory cache (6), ownership directory cache, event bridge, workspace lifecycle/routes, worktree routes, family transactions and evacuation.
  • Five existing UI worktree store tests passed with browser conditions.
  • Four Chromium tests passed: pointer/keyboard/touch actions, background completion racing an older HTTP response, immediate cached opening and Escape dismissal during a blocked refresh, and creation/selection by stable ID.
  • The isolated native location/worktree fixture passed against OpenCode 2.0.7, including real branch rename, cached reuse, lazy invalidation and forced validation with native events connected.
  • Server/UI typechecks, emitted server TypeScript, UI production build and diff check passed.

Installed state

Only the earlier compiled ownership-cache module (70af9c84) has been installed locally. The new inventory cache and selector changes are built and tested in the existing worktree but have not been deployed to the running installation. No shared daemon or user profile was restarted or modified for these new tests.

Keep the native event stream responsive when the shared daemon emits events from many unrelated directories. Sequential ownership misses previously replaced the inventory and its negative cache for every new directory, repeatedly running full worktree discovery and blocking the serial event bridge.

Allow one miss-triggered refresh per inventory cache lifetime. Further foreign directories share that refreshed snapshot, while TTL expiry and explicit worktree invalidation still discover external changes. Preserve concurrent singleflight loads and canonical path ownership checks.

Add a regression for twenty distinct foreign directories, negative-result reuse, explicit invalidation, and TTL expiry. It reproduces 21 inventory loads before the fix and requires two afterward. Validated the 63 targeted routing, workspace-manager, and directory tests, server typecheck, and emitted server build; measured the installed application's event backlog before and after a graceful relaunch.
@pascalandr

Copy link
Copy Markdown
Contributor Author

Follow-up live measurement (45 seconds, after restart): the multi-minute backlog is reduced, but this is not a complete startup/latency fix. Session event lag in the renderer had a median of 851 ms and p95 of 12.2 s, with overall spikes up to 15.5 s. Direct native events had session median 52 ms and p95 339 ms.

Startup resource timing also shows a 9.6 s worktree request and an 18.5 s project metadata request. Investigating those remaining inventory/hydration costs separately from this bounded miss-refresh correction.

@pascalandr

Copy link
Copy Markdown
Contributor Author

The remaining inventory cost is now isolated on the real repository: native location.get 22 ms, worktree.refresh 282 ms, worktree.list 5 ms for 165 entries; complete listNativeWorktrees took 24,067 ms and returned 165 owned worktrees. The implementation launches three Git commands per inspected checkout (495 for those entries, plus root checks), at up to eight concurrent checkouts. The dominant remaining cost is the local validation/annotation pass rather than the native list API. This PR bounds repetition of that pass; it does not eliminate its cold-start cost.

Retain successful native worktree inventories rather than only sharing in-flight requests. Serve warm display snapshots immediately and refresh stale data on demand in one shared background scan, without polling. Fence invalidated or disposed scans and retain the last display snapshot with retry backoff after failures.

Keep directory authorization on validated snapshots and force fresh family transaction inventories. Publish completed inventory changes to existing UI consumers and serialize their reload behind older HTTP responses. Open the worktree selector without waiting for I/O and suppress duplicate selection notifications so reconciliation cannot close it or trigger a family move.

Cover cache expiry, concurrency, mutation races, failures and disposal; real native rename/cache behavior with an isolated OpenCode 2.0.7 daemon; and Chromium selector updates, blocked refresh dismissal and pointer/keyboard/touch gestures. Passed 110 targeted server tests, five UI store tests, four browser tests, server/UI typechecks and builds. Cold scan cost and authoritative event-routing stalls remain separate from warm display caching.
@pascalandr pascalandr changed the title fix(server): prevent ownership refresh storms from delaying live events fix(worktrees): bound ownership refreshes and lazily cache inventories Sep 18, 2026
Require post-mutation inventory validation before returning display reads, propagate ownership misses through the lower inventory cache, and reject directory resolutions invalidated while loading. Preserve lazy display snapshots for passive refreshes while keeping create/remove read-your-writes semantics.

Coalesce UI refresh bursts into one in-flight read and a trailing refresh, retaining the last successful snapshot on reload errors. Keep selector options and inline actions focused through background updates and dismiss explicit reselection without moving the session. Document refresh conventions against existing catalogue, Git and virtualization behavior.

Add layered-cache and invalidation race regressions, native isolated manager create/remove coverage, and browser tests for stale HTTP responses, refresh bursts, keyboard focus and menu gestures. Synchronize the Git singleflight test mock with native ESM imports. Validation: 110 server tests, 7 UI store tests, 7 Chromium tests, isolated OpenCode 2.0.7 fixture, server/UI typechecks and UI build.
@github-actions

Copy link
Copy Markdown

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/35339219496

Artifacts expire in 7 days.
Artifacts:

  • pr-708-d46bdbc15d63e6051d22c4ffd320d00c6d143893-tauri-macos-arm64
  • pr-708-d46bdbc15d63e6051d22c4ffd320d00c6d143893-electron-windows
  • pr-708-d46bdbc15d63e6051d22c4ffd320d00c6d143893-tauri-windows
  • pr-708-d46bdbc15d63e6051d22c4ffd320d00c6d143893-tauri-macos
  • pr-708-d46bdbc15d63e6051d22c4ffd320d00c6d143893-tauri-linux
  • pr-708-d46bdbc15d63e6051d22c4ffd320d00c6d143893-electron-macos
  • pr-708-d46bdbc15d63e6051d22c4ffd320d00c6d143893-electron-linux
  • session-pruning-plugin-ubuntu-latest
  • session-pruning-plugin-windows-latest
  • session-pruning-plugin-macos-latest

@pascalandr
pascalandr merged commit 96ef38c into dev Sep 18, 2026
23 checks passed
@pascalandr
pascalandr deleted the fix/event-routing-refresh-storm branch September 18, 2026 11:58
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