Skip to content

Emit delete tombstone when provider_account_id changes#1320

Merged
BilalG1 merged 3 commits intodevfrom
fix-connected-accounts-ch-orphan-on-account-id-update
Apr 11, 2026
Merged

Emit delete tombstone when provider_account_id changes#1320
BilalG1 merged 3 commits intodevfrom
fix-connected-accounts-ch-orphan-on-account-id-update

Conversation

@BilalG1
Copy link
Copy Markdown
Collaborator

@BilalG1 BilalG1 commented Apr 10, 2026

The ClickHouse connected_accounts table is keyed by (project_id, branch_id, user_id, provider, provider_account_id), so updating providerAccountId in place on ProjectUserOAuthAccount orphaned the old CH sort-key row: the sync re-emitted the row under the new key while the old key sat untouched with no tombstone, and FINAL couldn't collapse them. verify-data-integrity would then report a row count mismatch for connected_accounts.

Record a delete tombstone for the old row state before the update runs, so the sync pipeline marks the stale sort key as deleted.

Summary by CodeRabbit

  • Bug Fixes
    • Improved OAuth account update handling to ensure external database synchronization is properly recorded when reassigning provider accounts to different account IDs.

The ClickHouse connected_accounts table is keyed by (project_id, branch_id,
user_id, provider, provider_account_id), so updating providerAccountId in
place on ProjectUserOAuthAccount orphaned the old CH sort-key row: the sync
re-emitted the row under the new key while the old key sat untouched with
no tombstone, and FINAL couldn't collapse them. verify-data-integrity would
then report a row count mismatch for connected_accounts.

Record a delete tombstone for the old row state before the update runs, so
the sync pipeline marks the stale sort key as deleted.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

The onUpdate handler in the oauth-providers CRUD module now detects when account_id differs from the existing providerAccountId and calls recordExternalDbSyncDeletion for the corresponding ProjectUserOAuthAccount row within the transaction before updating the record.

Changes

Cohort / File(s) Summary
OAuth Provider CRUD Update Handler
apps/backend/src/app/api/latest/oauth-providers/crud.tsx
Added logic to detect account_id changes in onUpdate and invoke recordExternalDbSyncDeletion for the ProjectUserOAuthAccount row when the providerAccountId differs from the new value.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • N2D4

Poem

🐰 An account ID changed with gentle care,
So sync deletion recorded there,
Within the transaction's embrace,
Keeping OAuth in its rightful place! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically describes the main change: emitting a delete tombstone when the provider_account_id field changes during an OAuth account update.
Description check ✅ Passed The description thoroughly explains the problem (ClickHouse orphaned rows due to missing tombstone), the root cause (keying by provider_account_id), and the solution (recording delete tombstone before update). It provides sufficient context for understanding the fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-connected-accounts-ch-orphan-on-account-id-update

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stack-auth-hosted-components Ready Ready Preview, Comment Apr 11, 2026 1:23am
stack-backend Ready Ready Preview, Comment Apr 11, 2026 1:23am
stack-dashboard Ready Ready Preview, Comment Apr 11, 2026 1:23am
stack-demo Ready Ready Preview, Comment Apr 11, 2026 1:23am
stack-docs Ready Ready Preview, Comment Apr 11, 2026 1:23am
stack-preview-backend Ready Ready Preview, Comment Apr 11, 2026 1:23am
stack-preview-dashboard Ready Ready Preview, Comment Apr 11, 2026 1:23am

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 10, 2026

Greptile Summary

This PR fixes a ClickHouse data integrity issue where updating providerAccountId on a ProjectUserOAuthAccount record left a stale orphaned row under the old sort key in the connected_accounts CH table. The fix records a delete tombstone (using the existing recordExternalDbSyncDeletion pattern) before the Prisma update runs, so the sync pipeline can correctly mark the old key as deleted while the new key gets a fresh row.

Confidence Score: 5/5

Safe to merge — targeted fix that correctly follows the established tombstone pattern.

The tombstone is recorded inside the same transaction, before the Prisma update, so to_jsonb("ProjectUserOAuthAccount".*) snapshots the old providerAccountId. This is exactly what the sync pipeline needs to delete the stale ClickHouse row. The approach mirrors the existing onDelete handler pattern and uses the correct oauthAccountId: params.provider_id argument. No P0/P1 findings.

No files require special attention.

Important Files Changed

Filename Overview
apps/backend/src/app/api/latest/oauth-providers/crud.tsx Adds a tombstone write via recordExternalDbSyncDeletion before updating providerAccountId, correctly snapshotting the old row state (including the old providerAccountId) so the sync pipeline can delete the stale ClickHouse sort-key row.

Sequence Diagram

sequenceDiagram
    participant Client
    participant onUpdate
    participant Prisma as Prisma (tx)
    participant DeletedRow as DeletedRow table
    participant CH as ClickHouse

    Client->>onUpdate: PATCH provider (new account_id)
    onUpdate->>Prisma: update allowSignIn / allowConnectedAccounts (if changed)
    onUpdate->>onUpdate: detect account_id changed
    onUpdate->>Prisma: recordExternalDbSyncDeletion (snapshots old providerAccountId)
    Prisma->>DeletedRow: INSERT tombstone with old row state (old providerAccountId)
    onUpdate->>Prisma: tx.projectUserOAuthAccount.update(new providerAccountId)
    Prisma-->>onUpdate: updated record
    onUpdate-->>Client: response

    Note over DeletedRow,CH: Sync pipeline picks up tombstone
    DeletedRow->>CH: DELETE row at old (project_id, branch_id, user_id, provider, old_account_id)
    Prisma->>CH: UPSERT row at new (project_id, branch_id, user_id, provider, new_account_id)
Loading

Reviews (1): Last reviewed commit: "Emit delete tombstone when provider_acco..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/backend/src/app/api/latest/oauth-providers/crud.tsx`:
- Around line 306-312: The tombstone for ProjectUserOAuthAccount is being
recorded after other mutations in the same transaction causing the snapshot to
capture a mutated row; move the call to recordExternalDbSyncDeletion(tx, {
tableName: "ProjectUserOAuthAccount", tenancyId: auth.tenancy.id,
oauthAccountId: params.provider_id }) to occur immediately after
existingOAuthAccount is loaded (i.e., before any updates/deletes to
ProjectUserOAuthAccount happen in this transaction) so the function snapshots
the true pre-change row; ensure tx, auth.tenancy.id, params.provider_id and
existingOAuthAccount are used unchanged when relocating the call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c982e76b-5fe2-4241-ab9d-3e8e746cc206

📥 Commits

Reviewing files that changed from the base of the PR and between f27a590 and 22fe620.

📒 Files selected for processing (1)
  • apps/backend/src/app/api/latest/oauth-providers/crud.tsx

Comment thread apps/backend/src/app/api/latest/oauth-providers/crud.tsx
@github-actions github-actions Bot assigned BilalG1 and unassigned N2D4 Apr 10, 2026
@BilalG1 BilalG1 merged commit 8292077 into dev Apr 11, 2026
32 checks passed
@BilalG1 BilalG1 deleted the fix-connected-accounts-ch-orphan-on-account-id-update branch April 11, 2026 01:46
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.

2 participants