Skip to content

fix(inngest): prevent duplicate XP awards via strict webhook idempotency checks #547#616

Open
prasiddhi-105 wants to merge 3 commits into
Coder-s-OG-s:mainfrom
prasiddhi-105:bugfix/xp-idempotency
Open

fix(inngest): prevent duplicate XP awards via strict webhook idempotency checks #547#616
prasiddhi-105 wants to merge 3 commits into
Coder-s-OG-s:mainfrom
prasiddhi-105:bugfix/xp-idempotency

Conversation

@prasiddhi-105

Copy link
Copy Markdown
Contributor

Summary

This PR resolves a critical distributed systems concurrency bug where a contributor could be awarded duplicate XP for a single merged Pull Request. By capturing and strictly validating the boolean return flag of insertXpEvent, we guarantee that parallel or retried GitHub webhook deliveries caught by our database unique index constraint (xp_events_idempotency) gracefully no-op and early-return instead of erroneously completing downstream processes.

Type of Change

  • Bug fix
  • New feature
  • UI / UX improvement
  • Refactor
  • Documentation
  • Other

Related Issue

Closes #547

What was changed?

  • Strict Return-Value Checking: Updated the unrecommended and recommended merge processing routines inside src/inngest/functions/process-pr-event.ts to evaluate the assignment of insertXpEvent().
  • Race Condition Prevention: Added an explicit if (!inserted) return; guard block immediately following the event creation inside awardRecommendedMerge(). This prevents identical concurrent webhook retry processing threads from bypass-updating row statuses, wiping active user caches, or writing duplicate rows to the transactional activity_log.
  • Clean Type Alignment: Ensured full return mapping structures conform tightly to the defined background pipeline return rules.

Checklist

  • My code follows the project structure and conventions
  • I tested this locally (npm run dev)
  • No hardcoded secrets or credentials
  • I have updated documentation if needed

@Siddhartha-singh01

Copy link
Copy Markdown
Collaborator

Review

Hey @prasiddhi-105 looked at this one. Needs a small change before merge.

Intent is good, and the PR is nice and small. CI is green too.

But a couple things:

  1. Duplicate XP is already blocked. insertXpEvent uses a unique constraint and onConflictDoNothing, so retries don’t double XP today. This PR doesn’t really fix Bug: XP award function is not idempotent, duplicate webhook deliveries grant double XP #547 the way the description says.

  2. The early if (!inserted) return is a bit risky. If XP was saved on the first try and then the job crashed before marking the recommendation completed, a retry will hit !inserted and bail and the rec can stay stuck open forever. Better: still complete the rec + clear cache on retry; only skip side effects that shouldn’t repeat (like activity_log which main already gates on inserted).

  3. Description vs diff: it says unrecommended + recommended paths both changed, but only awardRecommendedMerge did. Unrecommended still ignores the return value and always returns xpAwarded: true.

Ask: keep completing the recommendation on retries; only guard things that must run once; fix or drop the overstated PR text. Happy to re-check after that.

Thanks !

@jakharmonika364 jakharmonika364 added the Needs author reply Author need to reply label Jul 9, 2026
@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@prasiddhi-105 is attempting to deploy a commit to the codersogs-3057's projects Team on Vercel.

A member of the Team first needs to authorize it.

@prasiddhi-105

prasiddhi-105 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Review

Hey @prasiddhi-105 looked at this one. Needs a small change before merge.

Intent is good, and the PR is nice and small. CI is green too.

But a couple things:

  1. Duplicate XP is already blocked. insertXpEvent uses a unique constraint and onConflictDoNothing, so retries don’t double XP today. This PR doesn’t really fix Bug: XP award function is not idempotent, duplicate webhook deliveries grant double XP #547 the way the description says.
  2. The early if (!inserted) return is a bit risky. If XP was saved on the first try and then the job crashed before marking the recommendation completed, a retry will hit !inserted and bail and the rec can stay stuck open forever. Better: still complete the rec + clear cache on retry; only skip side effects that shouldn’t repeat (like activity_log which main already gates on inserted).
  3. Description vs diff: it says unrecommended + recommended paths both changed, but only awardRecommendedMerge did. Unrecommended still ignores the return value and always returns xpAwarded: true.

Ask: keep completing the recommendation on retries; only guard things that must run once; fix or drop the overstated PR text. Happy to re-check after that.

Thanks !

thank u for pointing out the partial failure crash recovery risk.

I've refactored the logic to ensure that recommendation updates and cache clearances execute unconditionally on webhook retries, ensuring no rows get left stuck open. The activity_log emission remains guarded under if (inserted). I also aligned the unrecommended merge block to return the actual inserted status flag. Ready for another look!

@Siddhartha-singh01

Copy link
Copy Markdown
Collaborator

Review

Hey @prasiddhi-105 rechecked after your updates. Still not ready to merge.

Thanks for trying to address the earlier feedback (always complete the rec, keep activity_log only when inserted, return the real flag for unrecommended). The direction is right, but the branch is broken right now.

What’s wrong:

  1. CI is red typecheck fails hard in process-pr-event.ts (lots of TS1005 errors).
  2. The file looks like a bad merge. handleMerge has half-written insertXpEvent calls, awardRecommendedMerge is duplicated/nested incorrectly, and tryLinkByIssueRef got folded into the wrong place. This won’t compile.
  3. Because of that, we can’t treat this as a real fix for Bug: XP award function is not idempotent, duplicate webhook deliveries grant double XP #547 yet.

What to do:

  1. Start clean from current main for process-pr-event.ts.
  2. Re-apply only the small intentional changes:
    • unrecommended: use const inserted = await insertXpEvent(...); return { xpAwarded: inserted }
    • recommended: always complete rec + clear cache on retry; only guard activity_log with if (inserted)
  3. No nested half-functions, no duplicate blocks.
  4. Push and make sure npm run typecheck + CI are green.

Ping me when that’s done and CI is green happy to re-review. Until then, please don’t merge.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: XP award function is not idempotent, duplicate webhook deliveries grant double XP

4 participants