Delivery threads: dedupe and enforce a unique key - #505
Merged
Merged
Conversation
createDeliveryThread is select-then-insert on (kind='delivery', runRef) with no unique index backing the read, so a redelivered routine event can create two delivery threads for one run. Assert the fix CL-7130 already established for root/reply threads: a concurrency test proving two racing createDeliveryThread calls converge on one row, and a migration test proving existing duplicate delivery threads get deduped (with references repointed) before the unique index is added. These currently fail: no unique index or insert-first path exists yet for delivery threads.
createDeliveryThread was select-then-insert on (kind='delivery', runRef) with no unique constraint backing the read, unlike its ensureRootThread/anchoredReplyThread siblings fixed for the same class of bug in CL-7130. A redelivered routine event could insert a second delivery thread for the same run, splitting replies across both. Migration 0026 dedupes any existing duplicate delivery threads (keeping the oldest row, repointing thread membership and message thread pointers off the dropped rows) and adds a partial unique index on workbench_threads scoped to kind='delivery' with a non-null run_ref. createDeliveryThread now inserts with onConflictDoNothing and re-selects on conflict, matching its siblings' pattern exactly. Fixes CL-7199.
TheGreatAxios
commented
Aug 30, 2026
TheGreatAxios
left a comment
Contributor
Author
There was a problem hiding this comment.
critique · reviewed, no blocking findings
Reviewed the two commits (tests, then implementation) against CL-7130's precedent for this exact bug class.
- packages/chat/src/migrations.ts:0026 — repoint-before-delete ordering correct (thread_id refs, then parent_thread_id, then delete duplicates, then index); byte-for-byte structural clone of the proven 0025 migration; null run_ref correctly excluded from dedupe to match the partial index predicate; runs inside the existing per-migration transaction so it's atomic; idempotent via IF NOT EXISTS and the migration ledger.
- packages/chat/src/threads.ts:594-604 — onConflictDoNothing arbiter (target columns + where predicate) matches workbench_threads_delivery_key in schema.ts exactly, same pattern as the root/reply paths.
- Verified independently: reverted the implementation commit and ran the new tests against real Postgres — 3 failures (missing migration, dedupe test, concurrency test diverging on ids). Restored HEAD — 7 pass, 0 fail. tsc clean.
- No signature change to createDeliveryThread; routes.ts's single caller unaffected.
No issues found in scope.
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.
Summary
createDeliveryThreadwas select-then-insert on(kind='delivery', runRef)with no unique constraint backing the read — unlike itsensureRootThread/anchoredReplyThreadsiblings in the same file, fixed for the same bug class in CL-7130. A redelivered routine event could insert a second delivery thread for the same run, splitting replies across both.0026_workbench_threads_delivery_keydedupes any existing duplicate delivery threads (keeping the oldest row pertenant_id, workbench_id, run_ref), repointingworkbench_thread_messages.thread_id,workbench_messages.thread_id, andworkbench_threads.parent_thread_idoff dropped rows before deleting them, then adds a partial unique index scoped tokind = 'delivery' AND run_ref IS NOT NULL.createDeliveryThreadnow inserts withonConflictDoNothing(arbiter predicate matching the index exactly) and re-selects on conflict — the identical structural pattern already used for root/reply threads.Fixes CL-7199.
Test plan
cd packages/chat && bun run typecheck— cleanbun test(full package suite, real Postgres) — 745 pass, 0 failcreateDeliveryThreadcalls for the same run converge on one rowbunx prettier --checkandbun run linton touched files — clean