Skip to content

BountiesService.expireOverdue() overwrites concurrently-advanced bounties to EXPIRED — read-then-save with no status re-check and no assertTransition #460

Open

Description

@chonilius

Problem

BountiesService.expireOverdue(), run hourly by BountyExpiryScheduler, loads every overdue OPEN, FUNDED or CLAIMED bounty and then saves them one at a time:

const overdue = await this.bountyRepo.createQueryBuilder('bounty')
  .where('bounty.deadline IS NOT NULL AND bounty.deadline < :now', { now: new Date() })
  .andWhere('bounty.status IN (:...statuses)', { statuses: [OPEN, FUNDED, CLAIMED] })
  .getMany();

for (const bounty of overdue) {
  bounty.status = BountyStatus.EXPIRED;
  await this.bountyRepo.save(bounty);
}

The status filter only applies at read time. Between the SELECT and each save(), and across a loop of per-row awaits that can take a while on a large backlog, a webhook can move the same bounty forward: CLAIMED → IN_REVIEW when a PR opens (#168), or on to MERGED and PAID. The save() then issues UPDATE ... SET status='expired' WHERE id=... from the stale in-memory copy and clobbers the newer status.

  • The loop also skips assertTransition(), unlike every other status change in BountiesService, so it doesn't consult the state machine (IN_REVIEW → EXPIRED isn't a valid transition).
  • A bounty that has just been merged and paid can be flipped to EXPIRED. Since EXPIRED → REFUNDED is allowed, the sponsor can then also refund a bounty that was already paid out.

Suggested fix

Replace the loop with one conditional update, so the status check and the write are atomic:

const result = await this.bountyRepo.createQueryBuilder()
  .update(Bounty)
  .set({ status: BountyStatus.EXPIRED })
  .where('deadline IS NOT NULL AND deadline < :now', { now })
  .andWhere('status IN (:...statuses)', { statuses: [OPEN, FUNDED, CLAIMED] })
  .execute();
return result.affected ?? 0;

Or, per row: update({ id, status: In([...]) }, { status: EXPIRED }) and count affected. Add a test where the bounty's status changes between the read and the write, and it isn't expired.

Related: #152, #266, #337, #347.

Activity

  1. added
    bugSomething isn't working
    very hardVery difficult task, expert-level effort required
    architectureArchitecture/design issue
    Stellar WaveIssues in the Stellar wave program
    on Sep 27, 2026
  2. Proxima84-code commented on Sep 27, 2026

    @Proxima84-code

    I have submitted a clean, atomic fix resolving this race condition in PR #468. Replaced the iterative save loop with a single atomic conditional UPDATE matching eligible statuses and verified with 14/14 passing unit tests.

  3. drips-wave commented on Sep 29, 2026

    @drips-wave

    Congratulations, @Ultra-Tech-code! 🎉 Your application was accepted by the repo's maintainers, and the issue is due on September 30, 2026.

    🧑‍💻 @Ultra-Tech-code: Please resolve the issue such that the repo's maintainers have enough time to review your contribution before the due date. You'll earn Points for completing the issue on-time, which will make you eligible for a share of the Stellar Wave Program's reward pool.

    Warning

    When opening a PR, please link it to this issue to ensure it gets tracked accurately. Points are awarded when this issue is marked as completed by the maintainer.

    🤠 Repo maintainers: Please keep an eye on the contributor's progress and review their work before the due date. You can manage this issue, including adjusting its complexity and points, here.

    🌊 Happy Wave 🌊

  4. Youngthreader commented on Oct 20, 2026

    @Youngthreader

    @Youngthreader has applied to work on this issue as part of the Stellar Wave Program's 10th wave.

    I will like to work on this issue maintainer it's in the area of my expertise

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @Youngthreader to this issue.

  5. drips-wave commented on Oct 21, 2026

    @drips-wave

    Congratulations, @Youngthreader! 🎉 Your application was accepted by the repo's maintainers, and the issue is due on October 22, 2026.

    🧑‍💻 @Youngthreader: Please resolve the issue such that the repo's maintainers have enough time to review your contribution before the due date. You'll earn Points for completing the issue on-time, which will make you eligible for a share of the Stellar Wave Program's reward pool.

    Warning

    When opening a PR, please link it to this issue to ensure it gets tracked accurately. Points are awarded when this issue is marked as completed by the maintainer.

    🤠 Repo maintainers: Please keep an eye on the contributor's progress and review their work before the due date. You can manage this issue, including adjusting its complexity and points, here.

    🌊 Happy Wave 🌊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Stellar WaveIssues in the Stellar wave programarchitectureArchitecture/design issuebugSomething isn't workingvery hardVery difficult task, expert-level effort required

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions