Skip to content

fix(bounties): replace read-then-save in expireOverdue with atomic status update guard (#460) - #481

Open
PINYOPATTANAWASANPORN wants to merge 2 commits into
MergeFi:mainfrom
PINYOPATTANAWASANPORN:fix/expire-overdue-atomic-update-460
Open

PINYOPATTANAWASANPORN wants to merge 2 commits into
MergeFi:mainfrom
PINYOPATTANAWASANPORN:fix/expire-overdue-atomic-update-460

Conversation

@PINYOPATTANAWASANPORN

Copy link
Copy Markdown

Summary of Changes

Fixed a concurrency race condition in BountiesService.expireOverdue() by replacing unconditional save() iterations with an atomic conditional status update guard (#460).

Root Cause / Technical Context

  1. expireOverdue() previously fetched overdue bounties in OPEN, FUNDED, or CLAIMED status and then called bountyRepo.save(bounty) on each item.
  2. If a concurrent operation transitioned a bounty to IN_REVIEW, MERGED, or PAID between getMany() and save(), save() overwrote the database row with EXPIRED, destroying the concurrent state transition without status re-checking.
  3. Fix details:
    • Replaced bountyRepo.save(bounty) with bountyRepo.update({ id, status: In([OPEN, FUNDED, CLAIMED]) }, { status: EXPIRED }).
    • Updated unit test suite in bounties.service.spec.ts validating atomic update execution and return counts.

Verification & Testing

Ran npm test -- src/bounties/bounties.service.spec.ts:

PASS src/bounties/bounties.service.spec.ts
  BountiesService
    √ creates a bounty in OPEN status (12 ms)
    √ funding an OPEN bounty locks escrow and moves it to FUNDED (3 ms)
    √ threads the linked GitHub issue id and deadline into escrow.fund (#158) (2 ms)
    √ rejects funding a bounty that is already FUNDED (15 ms)
    √ rejects claiming a bounty that is still OPEN (not yet funded) (2 ms)
    √ claim moves FUNDED -> CLAIMED and records the contributor (1 ms)
    √ markMergedAndRelease releases escrow to the contributor and moves to PAID (2 ms)
    √ markMergedAndRelease batches user lookups for team splits (3 ms)
    √ refund calls escrowService.refund and moves bounty to REFUNDED (2 ms)
    √ refund skips escrow refund when no escrow is linked (2 ms)
    √ expireOverdue flips overdue bounties to EXPIRED via atomic update guard (#460) (2 ms)
    √ markPrClosedWithoutMerge transitions IN_REVIEW back to CLAIMED (1 ms)
    √ markPrClosedWithoutMerge rejects bounties not in IN_REVIEW (2 ms)
    √ lists bounties with repository and language filters (2 ms)

Test Suites: 1 passed, 1 total
Tests:       14 passed, 14 total
Snapshots:   0 total
Time:        1.65 s

…atus update guard (MergeFi#460)

Ensure concurrent status transitions (e.g. IN_REVIEW / MERGED / PAID) are not overwritten to EXPIRED by using conditional update on valid status states.
…atus update guard (MergeFi#460)

Ensure concurrent status transitions (e.g. IN_REVIEW / MERGED / PAID) are not overwritten to EXPIRED by using conditional update on valid status states.
@vercel

vercel Bot commented Oct 4, 2026

Copy link
Copy Markdown

@PINYOPATTANAWASANPORN is attempting to deploy a commit to the chonilius' projects Team on Vercel.

A member of the Team first needs to authorize it.

This branch has not been deployed

No deployments
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