Fix account deletion failing on PostHog queued person deletion; report failures by stage
Account deletion fails before touching any data for every user who has a PostHog person. PostHog's bulk_delete now queues person deletion, so a successful 202 reports persons_deleted: 0 and persons_queued_for_deletion: N. Our completeness check required persons_found === persons_deleted, threw "PostHog account deletion returned an incomplete result", and parked the tombstone at failed. Production has 6 such rows since 2026-09-20 (latest 2026-10-08 02:55Z); the resume cron never selects failed.
The check now accepts persons_deleted + persons_queued_for_deletion === persons_found, and ignores the two deletion_errors steps PostHog documents as already deleted (log_activity, publish_clickhouse_tombstone). A short count still blocks deletion.
Failed attempts now call reportError with a synthetic AccountDeletionStageError, fingerprint ["account-deletion-failed", <stage>], and context limited to stage, trigger, retryable, tombstone status, and the original error class (no user id, no error message). /api/account joins the always-kept trace prefixes so DELETE and the resume cron GET are never dropped by the 2% sampler.
A pinned test confirms the hosted Subrouter path is not the cause: with SUBROUTER_HOSTED_URL and the token set and the host unreachable, a fresh deletion ends at hosted_delete_pending (resumable) with a 500 account_delete_retryable. Those rows resume only after SUBROUTER_HOSTED_URL is removed from production, which this PR does not do.
Tests (cmux-lawrence-2, bun test --isolate tests/account-route.test.ts tests/telemetry-sampler.test.ts): 5 fail at 91d49cea5d26 (test commit), 108 pass at 120c0923018d. backend-error-coverage.test.ts passes, bun run typecheck, bun run lint:complexity, and eslint on changed files are clean.
Changelog
Fixed: Account deletion no longer fails for users with analytics history.
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.
[!NOTE]
Medium Risk
Changes legal erasure flow and PostHog acceptance rules; mis-validation could block deletion or accept incomplete analytics removal, though new tests narrow the surface.
Overview
Fixes account deletion aborting at the PostHog step when bulk_delete queues person removal (persons_deleted: 0, persons_queued_for_deletion: N) instead of deleting synchronously. The completeness check now requires deleted + queued = found, still fails if events/recordings are not queued for a matched person, and treats only PostHog’s documented post-delete deletion_errors steps (log_activity, publish_clickhouse_tombstone) as non-blocking.
The deletion pipeline now tracks an AccountDeletionStage on progress and reports every failure to Sentry via reportAccountDeletionFailure: synthetic stage error, fingerprint account-deletion-failed/<stage>, context with trigger (user vs resume), retryable flag, and tombstone status—without user ids or raw error messages. /api/account is added to always-sampled trace prefixes so DELETE and the resume cron are not dropped by head sampling.
Tests cover queued PostHog success, invalid summaries, hosted subrouter unreachable → hosted_delete_pending, and Sentry scrubbing of deletion_stage.
Reviewed by Cursor Bugbot for commit 8f740c9053cd9b3592cbb1cf62bf507dcb767bdb. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Fixes account deletion failing for every user with a PostHog person. PostHog's bulk_delete now queues person deletion, so a successful response reports persons_deleted: 0 and persons_queued_for_deletion: N; the old check required persons_found === persons_deleted, parked the tombstone at failed, and the resume cron never selects failed. Production has six tombstones stuck this way since 2026-09-20.
- The completeness check now counts queued persons and ignores the two PostHog steps documented as already deleted (
log_activity,publish_clickhouse_tombstone); a short count or a missing events/recordings queue flag for a matched person still blocks deletion. - Failed attempts now call
reportErrorwith a syntheticAccountDeletionStageError, fingerprinted and tagged by stage (deletion_stage), with context limited to stage, trigger, retryable, and tombstone status — no user id or error message. /api/accountjoins the always-kept trace prefixes so DELETE and the resume cron are never dropped by the 2% sampler.
A pinned test shows the hosted Subrouter path is not the cause: with SUBROUTER_HOSTED_URL set and the host unreachable, a fresh deletion parks at hosted_delete_pending (resumable) with a 500 account_delete_retryable. Those rows resume only after SUBROUTER_HOSTED_URL is removed from production, which this PR does not do.
Summary by CodeRabbit
- Bug Fixes
- Improved account deletion handling for requests that are processed asynchronously, with validation of deletion results.
- Account deletion failures now provide clearer status and retry information while protecting sensitive account details.
- Improved monitoring of account deletion and retry operations to help identify issues.