Skip to content

fix: comprehensive GDPR user removal with performance optimization#314

Merged
RSO merged 4 commits into
mainfrom
opencode/proud-garden
Feb 18, 2026
Merged

fix: comprehensive GDPR user removal with performance optimization#314
RSO merged 4 commits into
mainfrom
opencode/proud-garden

Conversation

@RSO

@RSO RSO commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix 2 constraint bugs that could cause GDPR deletion to fail silently or error out for Kilo Pass subscribers and users with webhook triggers
  • Add 13 missing table deletions that were leaving orphaned user data in the database after GDPR removal
  • Optimize performance by breaking the single long-running transaction into 3 batched phases and parallelizing external service calls

Bug Fixes

kilo_pass_issuance_items RESTRICT blocker (critical)

The old code deleted credit_transactions before the user row, but kilo_pass_issuance_items holds a RESTRICT FK on credit_transactions.id. The cascade chain (kilo_pass_subscriptionskilo_pass_issuanceskilo_pass_issuance_items) only fires when kilocode_users is deleted — too late. This means GDPR deletion would fail for any Kilo Pass subscriber. Phase 2 now explicitly deletes the issuance chain before credit transactions.

cloud_agent_webhook_triggers RESTRICT risk

Has a RESTRICT FK on agent_environment_profiles.id. When user deletion cascades to profiles, this could block the cascade. Now explicitly deleted before the user row.

Missing Table Deletions

Previously orphaned after GDPR removal:

Table Data left behind
microdollar_usage_metadata Usage metadata (shared PK with microdollar_usage)
enrichment_data PII — GitHub/LinkedIn enrichment data
source_embeddings User's code embeddings
deployments User deployments + child tables (env vars, builds, events, threat detections)
code_indexing_search / code_indexing_manifest Code indexing data
referral_codes / referral_code_usages Referral system data
organization_memberships User remained as org member after deletion
organization_user_limits / organization_user_usage Per-user org limits and usage
free_model_usage Free model usage tracking
api_request_log Anonymized (kilo_user_id set to NULL, not deleted)

Performance Optimization

Before: Single transaction deleting all tables sequentially — could hold locks for minutes on heavy users, causing timeouts.

After: Three-phase approach:

  1. Phase 1 (batched, no transaction): Delete high-volume tables (microdollar_usage_metadata, microdollar_usage, api_request_log) in chunks of 5,000 rows. Each batch holds locks for milliseconds.
  2. Phase 2 (small transaction): Delete Kilo Pass issuance chain + credit transactions.
  3. Phase 3 (fast transaction): Delete remaining tables + user row. CASCADE handles ~18 additional tables automatically.

External service calls (Stripe, Customer.io, R2 blobs, v2 session DOs) now run concurrently via Promise.allSettled(), and v2 session deletions are batched in chunks of 10 concurrent requests.

Test Coverage

  • Kilo Pass subscriber deletion (catches the RESTRICT bug)
  • microdollar_usage_metadata cleanup
  • enrichment_data PII deletion
  • Referral codes and usages
  • Organization memberships, limits, and usage
  • free_model_usage deletion

RSO added 3 commits February 18, 2026 13:29
Fix missing table deletions and RESTRICT constraint bugs in the GDPR
user removal flow, and optimize performance by breaking the single
long-running transaction into batched phases.

Bug fixes:
- Fix kilo_pass_issuance_items RESTRICT FK blocking credit_transactions
  deletion for Kilo Pass subscribers
- Fix cloud_agent_webhook_triggers RESTRICT FK potentially blocking
  agent_environment_profiles cascade on user deletion
- Add 13 previously missing table deletions (enrichment_data,
  source_embeddings, deployments, code_indexing_*, referral_codes,
  referral_code_usages, organization_memberships, organization_user_*,
  free_model_usage, microdollar_usage_metadata, api_request_log)

Performance:
- Split single transaction into 3 phases to reduce lock duration
- Phase 1: batch-delete high-volume tables in chunks of 5000 rows
  outside a transaction (microdollar_usage_metadata, microdollar_usage,
  api_request_log)
- Phase 2: small transaction for Kilo Pass issuance chain + credits
- Phase 3: fast transaction for remaining tables + user row
- Parallelize external service calls (Stripe, Customer.io, R2, v2
  sessions) via Promise.allSettled
- Batch v2 session Durable Object deletions in chunks of 10 concurrent
  requests
@RSO RSO marked this pull request as ready for review February 18, 2026 12:36
@RSO RSO requested review from a team and markijbema February 18, 2026 12:37
@kilo-code-bot

kilo-code-bot Bot commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

This PR refactors the GDPR user deletion flow into a well-structured 3-phase approach and adds concurrent external service deletion. Key observations:

  • Phase 1 (batched pre-deletion) correctly handles high-volume tables outside a transaction with proper do...while batching to avoid long locks.
  • Phase 2 (Kilo Pass chain) properly resolves the RESTRICT FK constraint by deleting kilo_pass_issuance_items before credit_transactions.
  • Phase 3 (final deletion) correctly orders explicit deletes before the user row deletion, letting CASCADE handle the rest.
  • The Promise.allSettled usage in deleteUserFromExternalServices and deleteCliSessionV2Blobs is correct — errors are logged/reported without blocking other deletions.
  • Comprehensive test coverage for the new deletion targets (Kilo Pass chain, microdollar_usage_metadata, enrichment_data, referral codes, organization data, free_model_usage).
Files Reviewed (4 files)
  • src/lib/external-services.ts - 0 issues
  • src/lib/external-services.test.ts - 0 issues
  • src/lib/user.ts - 0 issues
  • src/lib/user.test.ts - 0 issues

@kilo-code-bot

kilo-code-bot Bot commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Overview

This PR significantly improves the GDPR user deletion flow with three well-designed changes:

  1. Batched high-volume table deletionmicrodollar_usage, microdollar_usage_metadata, and api_request_log are now deleted/anonymized in batches of 5,000 outside a transaction, preventing long-held locks and timeouts.

  2. Kilo Pass issuance chain handling — Correctly resolves the RESTRICT FK constraint on kilo_pass_issuance_items → credit_transactions by deleting the issuance chain first in a small transaction.

  3. Concurrent external service deletiondeleteUserFromExternalServices now uses Promise.allSettled for parallel execution, and deleteCliSessionV2Blobs uses batched concurrency (10 at a time) for HTTP DELETE calls.

The three-phase approach (batch pre-delete → Kilo Pass chain → final transaction) is a sound architecture that balances data integrity with performance. The new tables covered (enrichment_data, source_embeddings, deployments, code_indexing_*, referral_codes, referral_code_usages, organization_memberships, organization_user_limits, organization_user_usage, free_model_usage, cloud_agent_webhook_triggers) are all properly handled with correct FK ordering.

The test coverage is thorough, with dedicated tests for each newly covered table and the Kilo Pass RESTRICT constraint scenario.

Files Reviewed (4 files)
  • src/lib/external-services.ts - Concurrent external service deletion + batched v2 session cleanup
  • src/lib/external-services.test.ts - Test description update
  • src/lib/user.ts - Three-phase GDPR deletion with batching and new table coverage
  • src/lib/user.test.ts - Comprehensive tests for all new deletion scenarios

@RSO RSO merged commit cf0b05e into main Feb 18, 2026
12 checks passed
@RSO RSO deleted the opencode/proud-garden branch February 18, 2026 13:11
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