fix(script-executor): schedule member email deduplication sweep - #4485
fix(script-executor): schedule member email deduplication sweep#4485ramanathan1504 wants to merge 1 commit into
Conversation
PR SummaryHigh Risk Overview The sweep workflow now uses Merge correctness: Pagination for duplicate pairs switches from hash-based Reviewed by Cursor Bugbot for commit 9727bc0. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Pull request overview
Schedules the existing verified-email member deduplication workflow to run weekly.
Changes:
- Adds a Sunday 03:00 Temporal schedule using
SKIPoverlap behavior. - Registers the schedule during worker initialization.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
scheduleMemberDeduplication.ts |
Defines the recurring deduplication schedule. |
main.ts |
Registers the schedule at startup. |
Suppressed comments (2)
services/apps/script_executor_worker/src/schedules/scheduleMemberDeduplication.ts:17
- The overlap-policy rationale is useful, but allowed comments are limited to two lines. Please condense this performance constraint while preserving why
SKIPmust not be changed casually.
// The workflow walks the whole memberIdentities table via continueAsNew, so a single
// sweep can outlast the interval. Skip an overdue run instead of buffering it, so
// sweeps never stack up on top of each other.
services/apps/script_executor_worker/src/schedules/scheduleMemberDeduplication.ts:30
- This comment only narrates the empty initial argument and workflow paging behavior. That behavior is already clear from
args: [{}]and the workflow contract, so this violates the repository's no-descriptive-comments guideline.
// Start from the beginning of the hash ordering; the workflow pages itself from there.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (2)
services/apps/script_executor_worker/src/schedules/scheduleMemberDeduplication.ts:29
- This comment only restates the empty initial cursor represented by the argument, which the repository guidelines explicitly disallow. The workflow and argument names already make this behavior discoverable, so please remove the comment.
// Start from the beginning of the hash ordering; the workflow pages itself from there.
services/apps/script_executor_worker/src/schedules/scheduleMemberDeduplication.ts:16
- This allowed scheduling-constraint comment exceeds the repository's two-line maximum. Please condense it while preserving the reason for using
SKIP.
This issue also appears on line 29 of the same file.
// The workflow walks the whole memberIdentities table via continueAsNew, so a single
// sweep can outlast the interval. Skip an overdue run instead of buffering it, so
// sweeps never stack up on top of each other.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
services/apps/script_executor_worker/src/workflows/findAndMergeMembersWithSameVerifiedEmailsInDifferentPlatforms.ts:44
- Each call returns after starting an asynchronous
finishMemberMergingworkflow, so this 1,000-item page can enqueue up to 1,000 heavy merge workflows and immediately continue to the next page. The other bulk merge workflows explicitly cap runningfinishMemberMergingworkflows at 20 (cleanup/duplicate-members.ts:19-32,processLLMVerifiedMerges.ts:20-33) to avoid overloading PostgreSQL. Add equivalent backpressure and use a bounded batch size before enabling this recurring sweep.
await common.mergeMembersIfAllowed(couple.primaryMemberId, couple.secondaryMemberId)
|
On rollout — the schedule is registered live ( If you'd rather see the blast radius first, the workflow now accepts It still pages through the full table via Two notes:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
services/apps/script_executor_worker/src/activities/common.ts:61
- The expected in-progress
409is rethrown across the activity boundary, so Temporal applies the configured six activity attempts before the workflow can skip this couple. With the 3× backoff, each transitive collision can add roughly two minutes; a page containing many overlapping couples can therefore stall the weekly sweep for hours. Handle this known conflict inside the activity and return a skipped result, while continuing to throw unexpected failures.
} catch (error) {
svc.log.error({ err: error, primaryMemberId, secondaryMemberId }, 'Failed to merge members')
throw error
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
services/libs/data-access-layer/src/member_merge/index.ts:64
- This predicate scans both orientations, but the only index on
memberNoMergeis the primary key("memberId", "noMergeId")(V1666966941__initial.sql:685-689); there is no index starting withnoMergeId. Consequently each merge can sequentially scan the whole no-merge table here, aftergetMemberNoMergealready performed the same unindexed lookup for every sweep candidate. Add an index on"noMergeId"(ideally via a concurrent migration) before putting this path into the weekly sweep.
from "memberNoMerge"
where "memberId" = $(fromMemberId) or "noMergeId" = $(fromMemberId)
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Reviewed by Cursor Bugbot for commit 0048eb5. Configure here.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (1)
services/libs/data-access-layer/src/old/apps/script_executor_worker/member.repo.ts:46
- The grouping fix still leaves pagination lossy: multiple canonical pairs can share the 32-bit
hash, but the next page filters only withhash < afterHash. IfLIMIT 1000splits a collision, the remaining pairs are skipped. This is not reliably self-healing because no-merge and in-progress couples intentionally remain in the result set and can recreate the same boundary every week. Use(hash, leastMemberId, greatestMemberId)as the deterministic order and continuation cursor.
group by
Least(a."memberId", b."memberId"),
Greatest(a."memberId", b."memberId"),
hash
|
Verification update — I now have a working local toolchain, so the earlier "CI is the first real compile" caveat no longer applies. Static checks, clean across all three touched workspaces (
SQL executed against Postgres 16, on a fixture reproducing the identity shapes both integrations actually produce — a GitHub member with a verified
One item from this round is deliberately not coded: the no-merge read/merge race. Serializing it properly needs a lock taken on both the sweep and the UI's |
findAndMergeMembersWithSameVerifiedEmailsInDifferentPlatforms was registered in workflows.ts but nothing ever triggered it, so duplicate members accumulated until someone ran a sweep by hand. Register it as a weekly schedule and make the sweep safe to run unattended: carry no-merge edges onto the surviving member, skip couples that are blocked, already absorbed or mid-merge, and group and paginate by member pair so hash collisions can neither fabricate a couple nor drop one at a page boundary. Signed-off-by: Ramanathan <ramanathanbscmca@gmail.com>
9c79ec7 to
9727bc0
Compare

Fixes #4484
findAndMergeMembersWithSameVerifiedEmailsInDifferentPlatformsis registered inworkflows.tsbut nothing triggers it — no schedule, no cron job, no infra reference — so duplicate members accumulate until someone runs a sweep by hand.Change
SKIPoverlap, since a full sweep can outlast the interval).mergeMembersIfAllowed, which checksmemberNoMergefirst and carries the absorbed member's no-merge edges onto the survivor, so a separated pair cannot rejoin directly or transitively through a third member. MirrorsmergeIfAllowedindata_sink_worker; the existingmergeMembersis untouched for the other workflows.merge()throws 409 while a member has an IN_PROGRESS merge action andfinishMemberMergingclears that asynchronously, so in transitive duplicate groups one couple could abort the entire sweep.dryRunarg to log a full pass without merging before the schedule goes live.Detection is unchanged — the query still requires verified identities on both sides, so this adds no new trust in self-asserted commit emails.
No JIRA key on the title, so that check will warn; happy to retitle.