Repository navigation
fix(ui-server): commit reference sources and managed worktrees on the write worker - #3026
Merged
Merged
Conversation
…ites-offloop-044 # Conflicts: # docs/decisions/sqlite-write-worker.md
…ites-offloop-044 # Conflicts: # docs/decisions/http-api-boundary.md # docs/decisions/session-principals.md # docs/decisions/sqlite-write-worker.md # docs/plans/async-collaboration-requirements.md # docs/plans/async-collaboration.md # docs/plans/navigation-recovery.md
schlessera
marked this pull request as ready for review
October 26, 2026 17:11
This was referenced Oct 26, 2026
fix(ui-server): move storage scans to the write worker and guard against app-connection writes
#3029
Merged
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.
Closes #2999
Refs #2850
Reference sources and managed worktrees commit their
ui.dbwrites on the write worker atsynchronous=FULL. That includes the 15-second checkpoint observation.What changed
Every DB transaction in these services now runs verbatim in a worker procedure. They share one reference channel (
packages/ui-server/src/brain/reference-writes.ts), split into three modules:packages/ui-server/src/brain/reference-worktree-writes.tspackages/ui-server/src/brain/reference-source-writes.tspackages/ui-server/src/brain/reference-worktree-hygiene-writes.tsGit and filesystem work stays on the app thread. No transaction was split. Without a registered writer (tests, the cron runner) the same procedures commit inline.
What moved:
referenceWorktreeJournalManager). Both are reached fromattachWorktreeand the 15 s scope tick.access), renew, release, discard, open and close generation, expiry, reconcile, settle, removal receipts and reassessment.recoverstays inline.What callers wait for:
renewandreleasestill read their turn'slive()immediately before writing, inside the worktree'sserialsection. They await the worker commit inside that same section. A turn's end on that worktree (release, generation close, removal) runs through the sameserialqueue and the same worker FIFO, so a renewal whoselive()passed is ordered before the end's effects, as it was inline. A renewal that finds its turn ended still changes nothing.removeWorktree'sauthorize()works the same way.Why this shape
gpt-6-astra drafted it and stopped on the fence question above. Its proposal was worker-readable lifetime state. That isn't needed: holding the existing
serialsection through the commit keeps the same order. I made that ruling and added a test for it.Proof
Head
153f9ccd0. The change (drafted by gpt-6-astra, reviewed here; see "Why this shape"), a test fix (renewal test fails fast when the turn lifetime is never consulted) and merges oforigin/mainup to64af9b5a0(#3023), with doc conflicts only, resolved. Every heavy run went through the host slot wrapper, on this head.bun run check:pr --base origin/mainbun run test ./testsbun run test packages/ui-serverclose-cancels-shared-runstimed out waiting for its fixture'sstatsread to start (#2866, also seen on #3022).reference-transport's acquisition case was refusedworker_state_busyby the host-wide maintenance temp budget, which another process held at the time (#2859). That file passes 3/3 alone on this head.reference-writes-offloop, everyreference-*,*worktree*,turn-journal*,*hygiene*, schedules,app-writes-offlooptsc --noEmit,bun run lint,decision-citations,api-surfaceprove-native-host.ts, all four cells, underflockon the fixture lock,dd … conv=fsyncwriters outside the lock)An earlier round on
505138907hit the knownpolicy-write-boundaryPi shell-write flake (#2988) in./testsand thews-turn-budgetswindow (#3009) in the ui-server suite. Neither recurred here.Focused tests (
packages/ui-server/tests/reference-writes-offloop.test.ts, new; a real file-backedui.dband write worker). The app connection refuses writes to the reference tables, and the worker refuses any commit below FULL. The cases:Mutations (each applied, its named test run, restored):
APP_CONNECTION_WRITE reference_viewsREFERENCE_CHECKPOINT_LOOP_RUNS_WHILE_WORKER_HELDREFERENCE_ADMITTED_RENEWAL_COMMITSREFERENCE_RENEW_CONSULTS_TURN_LIFETIMEREFERENCE_GENERATION_NOT_BOUND_BEFORE_COMMITW4 first hung the test, which waited for a
live()call that never came. The wait is now bounded, and the mutant fails on its own label.Lag. None of the before/after attribution runs on this epic shows an app-connection turn over 20 ms from a reference module, in the native pi UID 1000 cell under three fsync writers. The cell's turns don't call the reference tools, and its scope ticks recorded no checkpoint-ledger turn of that size. So no window ≥ 250 ms was attributable to this group before or after, and the focused tests and mutations carry the evidence.
Checklist
.changeset/reference-writes-off-event-loop.md)api-report/ui-server.txtis unchanged.)docs/reflect the change, or nothing documented moved. (docs/decisions/sqlite-write-worker.md,docs/decisions/managed-worktrees.mdand shifted citations.)docs/decisions/provider-history.md): the provider-history guard passes, or nothing on a turn's path changed.Anything left open
The remaining work is #3002 (account and security administration, another worker) and #3004 (the final inventory and guard) under epic #2850.
🤖 Generated with Claude Code