fix: delete stripe_info when an org is removed#2580
Conversation
Org deletion canceled Stripe subscriptions but left stripe_info rows behind, inflating active subscriber counts. Clean up orphaned billing rows on delete and backfill existing orphans via migration. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Warning Review limit reached
More reviews will be available in 3 minutes and 54 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughAdds three complementary mechanisms to clean up ChangesOrphaned stripe_info cleanup
Sequence Diagram(s)sequenceDiagram
actor Operator
participant Script as cleanup_orphaned_stripe_info.ts
participant Supabase as Supabase (stripe_info / orgs)
participant Stripe as Stripe API
Operator->>Script: bun scripts/cleanup_orphaned_stripe_info.ts [--apply]
Script->>Supabase: Paginate all stripe_info rows
Supabase-->>Script: stripe_info pages
Script->>Supabase: Fetch orgs.customer_id in chunks of 200
Supabase-->>Script: linked org customer IDs
Script->>Script: Filter orphans via isActionableStripeCustomerId
Script->>Operator: Write CSV report
alt --apply flag present
Script->>Stripe: List subscriptions per orphan customer
Stripe-->>Script: subscription list
Script->>Stripe: Cancel each subscription
Stripe-->>Script: cancelled
Script->>Supabase: DELETE stripe_info WHERE customer_id = orphan
Supabase-->>Script: deleted count / error
Script->>Operator: Report success/failure counts
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
Stale comment
Risk: high. Not approving: Cursor Bugbot finished with 3 unresolved findings (including a high-severity billing cancel/delete ordering concern), and the Bugbot check did not pass cleanly. Assigned @Dalanir and @WcaleNieWolny for human review.
Sent by Cursor Approval Agent: Pull Request Approver
There was a problem hiding this comment.
Stale comment
Risk: medium. Cursor Bugbot completed as skipped and reported 3 potential issues, including a high-severity billing concern around deleting stripe_info after cancelSubscription. Human review is needed before merge; assigned WcaleNieWolny and Dalanir.
Sent by Cursor Approval Agent: Pull Request Approver External
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/cleanup_orphaned_stripe_info.ts`:
- Around line 140-162: The cleanup script logs failures but always exits with
code 0, preventing CI/ops systems from detecting incomplete cleanup. After the
asyncPool operation completes and the failure summary is logged, add a check
that sets process.exitCode to 1 when failures.length is greater than 0. This
ensures that when partial failures occur in the failures array, the process
exits with a non-zero status code to signal to callers that the cleanup was not
fully successful.
- Around line 127-129: The orphan filtering logic in the filter() call uses
isActionableStripeCustomerId which explicitly excludes customer IDs starting
with pending_, meaning these potential orphans are missing from both the CSV
report and the cleanup process. Either update the filtering condition to include
actionable pending_ prefixed customer IDs in the orphan set so they are properly
accounted for in the report and cleanup, or add clear documentation explaining
why pending_ customer IDs are intentionally excluded from the scope of this
orphan detection and remediation script.
- Around line 93-96: The `cancelStripeSubscriptions` function only fetches the
first 100 subscriptions with a hardcoded limit, leaving uncanceled subscriptions
for customers exceeding this threshold. Replace the single
`stripe.subscriptions.list` call with async iteration using a for-await loop to
automatically paginate through all subscription pages. Additionally, filter the
subscriptions to skip those that are already canceled before attempting to
cancel them, reducing unnecessary API calls while ensuring all active
subscriptions are properly canceled.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: c2dec5e7-7f95-4560-92bb-254d3b3b794e
📒 Files selected for processing (5)
package.jsonscripts/cleanup_orphaned_stripe_info.tssupabase/functions/_backend/triggers/on_organization_delete.tssupabase/migrations/20260623143000_cleanup_orphaned_stripe_info.sqltests/on_organization_delete.unit.test.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
Fix invalid visual-diff workflow YAML and register the visual diff config as a knip entry so dynamically imported exports are not flagged as unused. Co-authored-by: Cursor <cursoragent@cursor.com>
| .eq('customer_id', pendingCustomerId) | ||
|
|
||
| if (pendingStripeInfoDeleteError) { | ||
| cloudlog({ requestId: c.get('requestId'), message: 'failed to delete pending stripe_info during org delete', error: pendingStripeInfoDeleteError, customer_id: pendingCustomerId }) |
There was a problem hiding this comment.
Org delete ignores billing failures
Medium Severity
After org deletion, the handler always removes stripe_info and returns success even when Stripe cancellation or the database delete fails. cancelSubscription logs errors without rethrowing, so stripe_info can be deleted while subscriptions stay active, or delete errors can leave orphaned stripe_info rows with no retry.
Reviewed by Cursor Bugbot for commit 1291bcc. Configure here.
There was a problem hiding this comment.
Stale comment
Risk: high. Not approving: Cursor Bugbot completed as skipped and reports 4 unresolved findings, including a high-severity billing cancel/delete concern on the latest commit. Dalanir and WcaleNieWolny are already assigned for human review.
Sent by Cursor Approval Agent: Pull Request Approver
There was a problem hiding this comment.
Stale comment
Risk: high. Not approving: Cursor Bugbot completed as skipped with 4 unresolved findings (including billing cancel/delete ordering), and this Stripe org-cleanup change exceeds the low-risk approval threshold. @Dalanir and @WcaleNieWolny are already assigned for human review.
Sent by Cursor Approval Agent: Pull Request Approver External
Paginate Stripe subscription cancellation, include pending_ orphans in the cleanup script, exit non-zero on partial failures, and ensure org delete still removes stripe_info when Stripe cancel fails. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
There are 3 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 16c9590. Configure here.
| customerId: row.customerId, | ||
| error: error instanceof Error ? error.message : String(error), | ||
| }) | ||
| } |
There was a problem hiding this comment.
Apply skips DB on cancel
Medium Severity
In --apply mode, if cancelStripeSubscriptions fails for an orphaned stripe_info row, the script catches the error and skips deleting that stripe_info row from the database, leaving confirmed orphans behind.
Reviewed by Cursor Bugbot for commit 16c9590. Configure here.
| return !!orgId && !linkedOrgIds.has(orgId) | ||
| } | ||
|
|
||
| return isActionableStripeCustomerId(row.customerId) |
There was a problem hiding this comment.
Orphan rules differ from migration
Low Severity
isOrphanedStripeInfoRow treats non-pending_ rows with blank or whitespace-only customer_id as not orphaned, while the migration deletes any stripe_info row with no matching org. Dry-run counts and CSV output can understate what the migration removes.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 16c9590. Configure here.
|
There was a problem hiding this comment.
Risk: high. Not approving: Cursor Bugbot completed as skipped with 3 unresolved findings on the latest commit, and this Stripe billing cleanup exceeds the low-risk approval threshold. @Dalanir and @WcaleNieWolny are already assigned for human review.
Sent by Cursor Approval Agent: Pull Request Approver External







Summary
stripe_info(and pending placeholders) inon_organization_deleteafter canceling Stripe subscriptions, so org deletion no longer leaves orphaned billing rows20260623143000_cleanup_orphaned_stripe_info.sqlto remove existing orphanedstripe_inforows with no linked orgstripe:cleanup-orphaned-stripe-infoscript for dry-run/apply cleanup reports outside migrationsWhy
Org deletion only removed the
orgsrow and canceled Stripe subscriptions. Becauseorgs.customer_idreferencesstripe_info(not the other way around),stripe_inforows survived org deletion. That inflated Capgo active subscriber counts and produced rows like the 13 Stripe-canceled customers with no org.Test plan
stripe_infocount drops (expect ~13+ rows)stripe_inforow is removedbunx vitest run tests/on_organization_delete.unit.test.tsbun run stripe:cleanup-orphaned-stripe-info --env-file=./internal/cloudflare/.env.prodMade with Cursor
Summary by CodeRabbit
New Features
Improvements
Chores
Tests