Skip to content

fix: atomically pair XP insert and help request resolution - #848

Merged
jakharmonika364 merged 3 commits into
Coder-s-OG-s:mainfrom
ionfwsrijan:fix/issue-845-xp-help-request-atomic
Aug 1, 2026
Merged

fix: atomically pair XP insert and help request resolution#848
jakharmonika364 merged 3 commits into
Coder-s-OG-s:mainfrom
ionfwsrijan:fix/issue-845-xp-help-request-atomic

Conversation

@ionfwsrijan

Copy link
Copy Markdown
Contributor

Description

The "award-help-review" flow in process-review-event.ts uses two different database clients — Supabase service role for help request status updates, and Drizzle/postgres-js for XP event insertion — which cannot be wrapped in a single transaction. This creates three inconsistency scenarios:

  • Scenario A: Daily XP cap reached — help request is resolved but XP is not awarded, with best-effort rollback that may not succeed
  • Scenario B: Rollback doesn't filter by status = 'resolved', potentially overwriting concurrent state
  • Scenario C: Duplicate XP events (ON CONFLICT DO NOTHING) roll back the cap counter but leave help request resolved

Changes

  • Moved the help request status update from Supabase to Drizzle so it uses the same database connection as XP event insertion
  • Wrapped both operations in a single Drizzle transaction — both commit or both roll back atomically
  • Added an optional tx parameter to insertXpEvent and sumXpToday to accept a caller-managed transaction client
  • When tx is provided, insertXpEvent skips creating its own nested transaction and uses the caller's transaction directly

Closes #845

The XP award and help request status update used two different database
clients (Supabase for help_requests, Drizzle for xp_events), making them
impossible to wrap in a single transaction. This caused state
inconsistency when the daily XP cap was reached or duplicate events
occurred.

Fix:
- Move help request status update from Supabase to Drizzle so it uses
  the same database connection as XP event insertion
- Wrap both operations in a single Drizzle transaction — both commit
  or both roll back atomically
- Allow insertXpEvent to accept an optional transaction client (tx)
  for reuse within a caller-managed transaction
- Add db parameter to sumXpToday for consistent client usage

Closes Coder-s-OG-s#845
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

@ionfwsrijan 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.

@ionfwsrijan

Copy link
Copy Markdown
Contributor Author

@codersogs-code @Soumya-codr Please review this

@jakharmonika364

Copy link
Copy Markdown
Collaborator

please fix the CI

@ionfwsrijan

Copy link
Copy Markdown
Contributor Author

@jakharmonika364 @Soumya-codr Please review now

@jakharmonika364 jakharmonika364 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In events.ts's doDailyCapCheck - when a caller passes tx and the XP insert is a duplicate (result.length === 0), we skip client.rollback() (correctly, to avoid killing the caller's transaction) but never undo the xp_daily_usage.count increment either. It's not reachable from this PR's call site (the status='open' guard on the help-request update already prevents re-entry), but insertXpEvent(event, tx) is a shared util now - the next caller that hits a genuine duplicate mid-transaction will silently over-count that user's daily cap. Could you check-for-existing-row before incrementing instead of increment-then-maybe-undo? Also events.test.ts doesn't cover the tx-provided branch at all - worth a test for it either way.

@jakharmonika364 jakharmonika364 added Needs author reply Author need to reply and removed CI CD pending labels Jul 31, 2026
A caller-provided tx can't be rolled back mid-transaction, so a duplicate
insertXpEvent(event, tx) call used to silently inflate xp_daily_usage.count.
Check for the existing row first and skip the increment entirely. Also type
the tx param (was any) and cover the tx-provided branch in events.test.ts,
plus mock getDb in process-review-event.test.ts which the PR's atomic
transaction path requires.
@ionfwsrijan

Copy link
Copy Markdown
Contributor Author

@jakharmonika364 Please review now. I've made the changes

@jakharmonika364 jakharmonika364 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jakharmonika364 jakharmonika364 added level:advanced Advanced level difficulty quality:clean Clean, well-structured contribution type:bug Bug fix gssoc:approved Approved by GSSOC admin SSoC26 Hard ECSoC26 ECSoC26-L3 Hard good-pr good-backend and removed Needs author reply Author need to reply labels Aug 1, 2026
@jakharmonika364
jakharmonika364 merged commit 0132dff into Coder-s-OG-s:main Aug 1, 2026
2 of 3 checks passed
@ecsoc-sentinel ecsoc-sentinel Bot added ECSoC26-L2 Medium and removed ECSoC26-L3 Hard labels Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ECSoC26-L2 Medium ECSoC26 good-backend good-pr gssoc:approved Approved by GSSOC admin Hard level:advanced Advanced level difficulty quality:clean Clean, well-structured contribution SSoC26 type:bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] XP Insert and Help Request Status Not Atomically Paired — State Inconsistency

2 participants