Repository navigation
fix(ui-server): commit retries, policy proposals, drafts, transcription claims and settings on the write worker - #3023
Merged
Conversation
…t-writers-offloop-044 # Conflicts: # docs/decisions/sqlite-write-worker.md
…-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 # packages/ui-server/src/ws/session-catalog-writes.ts
…-offloop-044 # Conflicts: # docs/decisions/sqlite-write-worker.md # docs/plans/async-collaboration-requirements.md # docs/plans/async-collaboration.md
…-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
…-offloop-044 # Conflicts: # docs/decisions/sqlite-write-worker.md
# Conflicts: # docs/decisions/sqlite-write-worker.md
schlessera
marked this pull request as ready for review
October 26, 2026 16:21
This was referenced Oct 26, 2026
This was referenced Nov 3, 2026
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 #2998
Refs #2850
The remaining foreground chat writers commit on the
ui.dbwrite worker atsynchronous=FULL: retained retries, policy proposals, composer drafts, voice transcription claims and settings.What changed
Each moved write is the statement or transaction that ran inline, run in a worker procedure. Without a writer (tests, the cron runner) the same code runs inline.
Retained retries (
packages/ui-server/src/ws/session-catalog-writes.ts,packages/ui-server/src/ws/retry-requests.ts):peekRetryand replayed history stop showing the retry immediately. If the invalidation's delete fails, the worker keeps refusing a reservation of that obsolete request, and the failure is reported.session_work) names a later request than the retained turn. A test simulates a failed delete followed by a restart.Policy proposals (
packages/ui-server/src/brain/policy-proposal-writes.ts):settled().Composer drafts (
packages/ui-server/src/drafts/writes.ts):DraftErrorkeeps its class, status and body.acceptDraftalready ignored the outcome, and a failure is logged by identity only. All draft writes share one worker channel, so a later save follows the tombstone.Voice transcription (
packages/ui-server/src/voice/transcription-writes.ts):Settings (
packages/ui-server/src/db/settings-writes.ts): one settings writer owns these, and each consumer awaits the commit before its dependent read or answer:Why this shape
The question in each case was what a caller relied on when the write committed inline.
session_infohandler. Making it awaited would let later backend events overtake it. Its outcome was never used, so it is submitted, and the reads wait instead.gpt-6-astra drafted both slices; I reviewed them and added fixes:
saved-transcription.test.tsnow waits for the provider call instead of a fixed 20 ms.Proof
Head
b6a5a0ff5. The two slices (each drafted by gpt-6-astra, reviewed and fixed here; see "Why this shape"), one merged into the other, then merges oforigin/mainup tofd51cadf4(#3015), resolving doc conflicts and thesession-catalog-writes.tsoverlap with #3011's local exchanges. The full suites and native proof ran on57356800e, which contains mainb99489b3c(#3014). The last merge brought in #3015 (schedules:app.tswiring and this decision doc); on this head I rerancheck:pr, typecheck, lint, citations and the focused tests. Every heavy run went through the host slot wrapper.bun run check:pr --base origin/mainonb6a5a0ff5(and on57356800e)bun run test packages/ui-serveron57356800ebun run test ./testson57356800epolicy-write-boundary's Claude interactive case,AREAS_WRITABLE(worker scratch unwritable), already open as #3010, #2987, #2923 and #2988. Interleaved runs alone failed one case in 15 on main from a worktree beside this one, and 5 in 15 here, always in a scratch, shell-write or Git-lock case. #2999's branch hit it too, and none of these cases runs the writers this PR moves.b6a5a0ff5:retry-proposal-offloop,drafts-settings-offloop, drafts, transcription, settings, models, tool permissions, digest, subscription,ws-activity, the allowlist and approval channel, retry, policy proposals,app-writes-offloop, dispatch, local exchanges, classification, schedulestsc --noEmit,bun run lint,decision-citations,api-surfaceonb6a5a0ff5prove-native-host.ts, all four cells, underflockon the fixture lock,dd … conv=fsyncwriters outside the lock) on57356800eFocused tests (
retry-proposal-offloop.test.tsanddrafts-settings-offloop.test.ts, both new). The app connection refuses writes to the watched tables, and the worker refuses any commit below FULL.Mutations (each applied, its named test run, restored):
APP_CONNECTION_WRITE retry_requestsRETRY_PENDING_CLEAR_HIDDENRETRY_FAILED_CLEAR_FAILS_CLOSEDRETRY_PENDING_RESERVATION_NO_ACKRETRY_RESTART_SUPERSEDED_FAILS_CLOSEDPOLICY_REVIEW_EMITTED_AFTER_COMMITPOLICY_PENDING_INSERT_DUPLICATE_APPROVES_ONCEAPP_CONNECTION_WRITE policy_proposalsAPP_CONNECTION_WRITE session_draft_attachmentsDRAFT_CONFLICT_REVIVED_CLASSDRAFT_GET_WAITS_FOR_ACCEPT_COMMITTRANSCRIPTION_ROUTE_ANSWERED_BEFORE_CLAIM: the route answers 500 because its inline write hit the app-connection refusalTRANSCRIPTION_NO_DISPATCH_BEFORE_CLAIM_COMMITTRANSCRIPTION_DUPLICATE_REFUSEDAPP_CONNECTION_WRITE settingsSeveral first passes failed for the wrong reason. These now fail on their named guards:
expectthrown inside a WebSocket send, which dispatch's catch swallowed.Lag. The native pi UID 1000 cell under three fsync writers drives none of these writers. The before-runs recorded for #2996 and #2997 showed no app-connection turn over 20 ms from any of them, so the focused tests and mutations carry the evidence.
Checklist
.changeset/retries-proposals-off-event-loop.md,.changeset/drafts-settings-off-event-loop.md)api-report/ui-server.txtis unchanged.)docs/reflect the change, or nothing documented moved. (docs/decisions/sqlite-write-worker.md; shifted and re-pointed citations.)docs/decisions/provider-history.md): the provider-history guard passes, or nothing on a turn's path changed.Anything left open
The remaining groups are the open sub-issues of epic #2850.
🤖 Generated with Claude Code