Skip to content

fix(code-reviews): serialize webhook create and retrigger races - #6180

Closed
pandemicsyn wants to merge 1 commit into
mainfrom
investigate/github-review-dedup-20260915
Closed

pandemicsyn wants to merge 1 commit into
mainfrom
investigate/github-review-dedup-20260915

Conversation

@pandemicsyn

@pandemicsyn pandemicsyn commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes a class of Postgres 23505 unique-violation errors on the UQ_cloud_agent_code_reviews_active_provider_publisher partial index (single-active-provider-publisher invariant), reported as Sentry issue KILOCODE-WEB-279B. Scoped to the GitHub webhook/lifecycle path.

Two paths treated a legitimate invariant conflict as an unhandled exception: the webhook create path ran supersede, same-SHA lookup, and a plain INSERT as separate autocommit statements with no serialization; and resetCodeReviewForRetry set a terminal review back to pending without checking whether another active provider-publishing review already held the same integration/repo/PR slot.

  • createCodeReviewForWebhook performs the same-SHA re-check, supersede, and ON CONFLICT DO NOTHING insert inside a per-change pg_advisory_xact_lock, resolving the winner instead of raising. The unique index remains the source of truth.
  • resetCodeReviewForRetry mirrors the index predicate and refuses the reset while another active provider review holds the slot; a residual unique violation is mapped to a conflict result.
  • Check-run persistence is status-aware: attachCheckRunIdIfActive (plus a merge-commit repoint CAS) attaches the GitHub check id only while the review is still active. If the review was superseded, the newly created external check is cancelled and the handler returns without reacting or dispatching. Raw cancel queries project the post-update check_run_id so a supersede that blocks behind an in-flight attach still tears the check down.
  • No schema or migration changes; the partial index and single-active invariant are preserved, and unrelated DB errors still surface.

Verification

No manual validation was performed. The DB concurrency behavior cannot be exercised by hand reliably; it is covered by automated tests that reproduce competing webhook creation, concurrent retrigger resets, and the check-run attach/cancel ordering (exact commands and results are in the completion report).

Visual Changes

N/A

Reviewer Notes

Webhook review creation and retrigger could both violate the single-active-provider-publisher partial unique index (UQ_cloud_agent_code_reviews_active_provider_publisher) with Postgres 23505.

- GitHub webhook create supersedes and inserts inside a per-change advisory lock with ON CONFLICT DO NOTHING and a winner fallback, so overlapping deliveries cannot both insert an active review.
- resetCodeReviewForRetry refuses to revive a terminal review while another active provider review holds the same integration/repo/PR slot, and maps a residual unique violation to a conflict instead of throwing.
- Check run attachment is status-aware (attachCheckRunIdIfActive, plus a merge-commit repoint CAS): when the review has already left the active set the newly created external check is cancelled instead of being attached to a cancelled row. Cancel queries report the post-update check id so a supersede that blocks behind an in-flight attach still tears the check down.

Scoped to the GitHub webhook and review lifecycle path. GitLab/Bitbucket create paths and the retrigger gate re-check remain unchanged follow-ups.

Tests: focused code-review DB and GitHub pull-request handler suites, plus typecheck, lint, and formatting.
@pandemicsyn
pandemicsyn marked this pull request as ready for review September 15, 2026 19:58
const winner =
(await findExistingReviewWithDatabase(tx, scope, params.headSha)) ??
(params.platformIntegrationId
? await findActiveProviderPublishingReviewWithDatabase(tx, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: The surviving-conflict assumption does not hold under READ COMMITTED, so a newer push can be silently dropped

The comment above asserts a surviving conflict must be a manual provider review, but cancelReviewsForPR is a separate statement with its own snapshot. A concurrent resetCodeReviewForRetry — which does not take codeReviewWebhookLockKey — can commit between that cancel and this ON CONFLICT DO NOTHING insert, reviving an existing failed/cancelled review for an older SHA to pending. The insert then skips, findExistingReviewWithDatabase misses (different head_sha), and this lookup returns the just-revived older-SHA review as the winner. The caller's !creation.created branch then returns 200 A code review is already active for this pull request, so GitHub treats the delivery as successful and does not redeliver, while the new head SHA is never reviewed. The pre-insert cancel would have superseded that review had the reset committed before it. Consider re-running the cancel and retrying the insert when the winner's head SHA differs from params.headSha, or serializing the reset path on the same advisory lock.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

prNumber: params.prNumber,
})
: null);
if (!winner) throw new Error('Code review webhook conflict winner not found');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: A resolvable conflict can still surface here as an unhandled 500

findExistingReviewWithDatabase is owner- and platform-scoped (reviewScopeConditions), and findActiveProviderPublishingReviewWithDatabase only matches active, provider-publishing rows. A conflict on UQ_cloud_agent_code_reviews_webhook_integration_repo_pr_sha can satisfy neither lookup: e.g. a terminal same-integration/repo/PR/SHA row whose owner no longer matches (owner is derived from integration.owned_by_organization_id / owned_by_user_id) is invisible to both, so winner is null and this throws. That yields a webhook 500 plus a Sentry event on every redelivery for that SHA — the failure class this PR set out to remove. The sibling createCodeReviewIfAbsentInTransaction (lines 1754-1756) already handles exactly this with the owner/platform-agnostic findReviewByLegacyUniqueKeyWithDatabase; adding it as a third fallback keeps the conflict resolvable.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

// The review was cancelled or superseded after the new gate was created;
// tear it down rather than leaving a queued check on the merge commit.
await cancelMergeCommitCheckRun(newCheckRunId);
logExceptInTest(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SUGGESTION: This log reports a cancellation even when the cancel failed

cancelMergeCommitCheckRun (line 652) swallows updateCheckRun failures, so execution reaches this line unconditionally and logs cancelled check run … for a gate that may still be queued on the merge commit — the exact branch-protection block this branch is trying to avoid. Return a success flag from the helper, or move the log next to the updateCheckRun call inside the try.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 1

Fix these issues in Kilo Cloud

Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/code-reviews/db/code-reviews.ts 420 A surviving conflict can be a just-revived older-SHA review, so the new push is silently dropped while the webhook returns 200
apps/web/src/lib/code-reviews/db/code-reviews.ts 426 A legacy-index conflict outside the owner/platform-scoped lookups makes the winner lookup null and raises a webhook 500

SUGGESTION

File Line Issue
apps/web/src/lib/integrations/platforms/github/webhook-handlers/pull-request-handler.ts 706 Success log is emitted unconditionally although cancelMergeCommitCheckRun swallows cancel failures
Files Reviewed (4 files)
  • apps/web/src/lib/code-reviews/db/code-reviews.ts - 2 issues
  • apps/web/src/lib/code-reviews/db/code-reviews.test.ts - 0 issues
  • apps/web/src/lib/integrations/platforms/github/webhook-handlers/pull-request-handler.ts - 1 issue
  • apps/web/src/lib/integrations/platforms/github/webhook-handlers/pull-request-handler.test.ts - 0 issues

Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

@pandemicsyn pandemicsyn closed this Oct 1, 2026
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