Skip to content

Fix account lifecycle export and deletion safety - #890

Merged
kody-bot merged 36 commits into
mainfrom
cursor/review-fix-account-lifecycle-aeba
Jul 23, 2026
Merged

kody-bot merged 36 commits into
mainfrom
cursor/review-fix-account-lifecycle-aeba

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Jul 22, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • use non-expiring per-token leases with exact audited admin repair; centralized web middleware leases signed-in mutation requests, including MCP server web actions
  • cold MCP maintenance ownership/purge reads the Agents SDK persisted props key directly; stored-data objects without proven owners block completion
  • dry-run-first control-plane backfill remains required before deletion marker version 1; production sweep is not executed here
  • redact foreign repair-audit parties and replace aggregate DO summary arrays with kind-specific cursor paging plus paged remote connector content

Operator rollout

  • Dry run: npm run backfill:mcp-agent-sessions -- --origin https://heykody.dev --worker-script kody --audit-out /tmp/mcp-agent-session-backfill-dry-run.json
  • Execute after audit review with --execute; any no_owner, conflict, or failure prevents completion
  • Required env: CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN, CAPABILITY_REINDEX_SECRET

Validation

  • focused final-blocker tests: 5 files, 20 tests passed
  • npm run validate passed on final revision a9e9360d
  • unit/Workers: 364 files, 1,234 tests passed
  • Playwright: 17 passed
  • MCP E2E: 2 passed
  • migration ledger: 94 files, next prefix 0089
  • format, lint, typecheck, backup build, primitives, and migrations checks passed
System recap — extends existing primitives (medium risk)

Mode: recap · Base: main @ a6f027da · Head: a9e9360d

Invariants

  • Cold MCP DO ownership is read from persisted SDK props, never volatile instance props.
  • Signed-in web mutations hold a full-request lease; account deletion remains exempt for retry.
  • Repair audit exports preserve each viewer’s own id while redacting the foreign party.
  • DO discovery/content is cursor-paged per surface without aggregate id arrays.
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Account exports now include an r2_object section for user-owned media/attachments, with resumable, chunked downloads and bounded metadata.
    • The account export endpoint is now manifest-only (bounded metadata); full export is obtained via follow-up sections, with updated filename/header.
  • Bug Fixes

    • Account deletion now uses a “deleting” state plus per-account write protection to prevent new writes during deletion, and improves cleanup retry behavior on failures.
    • Export privacy redaction for community/social metadata is tightened to include only participant-owned signals.
  • Documentation

    • Updated privacy/storage cleanup and export/deletion contracts to reflect the new behavior.
  • Chores

    • Improved cleanup for legacy package retriever cache/index entries.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds account-deletion markers and write leases, makes deletion inventory-driven and atomic, adds strict R2 cleanup, introduces bounded chunked R2 exports, narrows exported community data, and removes legacy package-retriever keys.

Changes

Account lifecycle enforcement

Layer / File(s) Summary
Deletion state and write leases
packages/worker/migrations/*, packages/worker/src/app/account-deletion-state.ts, packages/worker/src/db.ts, packages/worker/src/**/test-schema.ts
Users gain deletion timestamps and active-write lease fields, with helpers for marking deletion, rejecting writes, and releasing leases.
Write-path deletion checks
packages/worker/src/app/authenticated-user.ts, packages/worker/src/community/*, packages/worker/src/email/*, packages/worker/src/jobs/service.ts, packages/worker/src/mcp*, packages/worker/src/package-invocations/*
Authenticated, email, avatar, icon, job, MCP, memory, and package-invocation paths now recognize deleting accounts and use write leases.
Strict account deletion
packages/worker/src/app/account-deletion.ts, packages/worker/src/app/handlers/account-delete.ts, packages/worker/src/app/account-deletion.node.test.ts, docs/contributing/architecture/data-storage.md
Deletion stages the account, inventories owned data, fails closed on inventory or cleanup errors, and atomically removes D1 rows only after cleanup succeeds.
Strict R2 prefix cleanup
packages/worker/src/app/account-r2-prefix-cleanup.ts, packages/worker/src/app/account-r2-prefix-cleanup.node.test.ts
R2 prefix deletion paginates, validates key scope, deletes historical objects, and propagates listing or deletion failures.

Account export and storage contracts

Layer / File(s) Summary
Export surface contracts
packages/worker/src/app/account-data-targets.ts, packages/worker/src/app/account-user-owned-surfaces.ts, docs/use/privacy.md, docs/contributing/architecture/data-storage.md
Community feedback data is excluded from participating-user exports, foreign report identities are redacted, and chunked R2 and usage-rollup storage surfaces are registered.
R2 inventory and chunked export
packages/worker/src/app/account-r2-inventory.ts, packages/worker/src/app/account-r2-export.ts, packages/worker/src/app/account-export.ts, packages/worker/src/app/handlers/account-export.ts, packages/worker/src/app/account-export.node.test.ts
R2 references are counted and exported one bounded chunk at a time using signed cursors, ownership checks, ETag/size validation, mutation markers, and missing-object results. The browser route now returns a manifest-only payload.

Package retriever cache cleanup

Layer / File(s) Summary
Legacy retriever index removal
packages/worker/src/package-retrievers/manifest-cache.ts, packages/worker/src/package-retrievers/manifest-cache.node.test.ts
Refresh, package removal, and user-wide cache deletion directly remove legacy search and context index keys and include them in deletion counts.

Migration registration

Layer / File(s) Summary
Migration registration and validation
packages/worker/migrations/*, tools/migration-ledger.json, tools/check-migrations.node.test.ts
Three migrations are registered in the ledger, and migration validation expectations use the updated numbering.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main export and deletion safety changes in the PR.
✨ 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 cursor/review-fix-account-lifecycle-aeba

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.

@cursor
cursor Bot force-pushed the cursor/review-fix-account-lifecycle-aeba branch from 7dbf633 to 9a6b2e7 Compare July 22, 2026 23:47
@kody-bot
kody-bot marked this pull request as ready for review July 22, 2026 23:49
@github-actions

github-actions Bot commented Jul 22, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-890.kody-a99.workers.dev

Worker: kody-pr-890
D1: kody-pr-890-db
KV: kody-pr-890-oauth-kv

Mocks:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/worker/src/community/avatar.ts (1)

223-251: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

deleteUserAvatar isn't lease-protected — TOCTOU with account deletion.

Unlike saveUserAvatar, this function checks assertAccountWritableDb once and then performs an unguarded UPDATE (no deleting_at IS NULL clause, no .meta.changes check) without holding withAccountWriteLease. Between the check and the update, markAccountDeleting can start (and, since this write never incremented active_write_count, the deletion won't detect it as an active writer), letting deletion cleanup race with this in-flight delete.

🔒 Proposed fix to lease-protect the delete path
 export async function deleteUserAvatar(input: {
 	env: Pick<Env, 'APP_DB' | 'COMMUNITY_ASSETS'>
 	numericUserId: number
 	stableUserId: string
 }): Promise<void> {
-	await assertAccountWritableDb(input.env.APP_DB, input.stableUserId)
-	const existing = await input.env.APP_DB.prepare(
-		`SELECT avatar_key FROM users WHERE id = ?`,
-	)
-		.bind(input.numericUserId)
-		.first<{ avatar_key: string | null }>()
-	const previousKey =
-		existing?.avatar_key == null ? null : String(existing.avatar_key)
-
-	await input.env.APP_DB.prepare(
-		`UPDATE users
-		SET avatar_key = NULL, updated_at = ?
-		WHERE id = ?`,
-	)
-		.bind(utcSqliteTimestamp(), input.numericUserId)
-		.run()
-
-	if (!previousKey) return
-	try {
-		await input.env.COMMUNITY_ASSETS.delete(previousKey)
-	} catch (error) {
-		console.error('user-avatar-delete-failed', previousKey, error)
-	}
+	await withAccountWriteLease({
+		db: input.env.APP_DB,
+		stableUserId: input.stableUserId,
+		async write() {
+			await assertAccountWritableDb(input.env.APP_DB, input.stableUserId)
+			const existing = await input.env.APP_DB.prepare(
+				`SELECT avatar_key FROM users WHERE id = ?`,
+			)
+				.bind(input.numericUserId)
+				.first<{ avatar_key: string | null }>()
+			const previousKey =
+				existing?.avatar_key == null ? null : String(existing.avatar_key)
+
+			const update = await input.env.APP_DB.prepare(
+				`UPDATE users
+				SET avatar_key = NULL, updated_at = ?
+				WHERE id = ? AND deleting_at IS NULL`,
+			)
+				.bind(utcSqliteTimestamp(), input.numericUserId)
+				.run()
+			if ((update.meta.changes ?? 0) !== 1) {
+				throw new Error(
+					'Avatar delete was rejected because account state changed.',
+				)
+			}
+
+			if (!previousKey) return
+			try {
+				await input.env.COMMUNITY_ASSETS.delete(previousKey)
+			} catch (error) {
+				console.error('user-avatar-delete-failed', previousKey, error)
+			}
+		},
+	})
 }

Would you like a matching regression test added, mirroring the existing saveUserAvatar removes an in-flight upload when deletion starts case in avatar.node.test.ts?

🤖 Prompt for 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.

In `@packages/worker/src/community/avatar.ts` around lines 223 - 251, Update
deleteUserAvatar to perform the account validation, avatar lookup, database
update, and asset deletion within withAccountWriteLease, matching
saveUserAvatar’s lease-protected flow. Guard the UPDATE with deleting_at IS NULL
and verify its meta.changes result so deletion cannot race with account cleanup
or proceed after the account enters deletion.
🤖 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 `@packages/worker/src/app/account-deletion.ts`:
- Around line 1029-1044: Avoid adding the EMAIL_BLOBS inventory deletion count
to result.deletedEmailBlobs after deleteAccountEmailBlobPrefixes already removes
those canonical prefixes. Remove the redundant deleteR2Objects count update, or
filter its keys to exclude the deleted email and attachment prefixes while
preserving safe cleanup.

In `@packages/worker/src/email/service.ts`:
- Around line 162-167: Update the catch block around the email insertion flow to
make the blobs.delete cleanup best-effort: handle or suppress any rejection from
deleting rawMimeKey, then always rethrow the original caught error. Preserve the
existing null check and cleanup attempt.

In `@packages/worker/src/mcp-auth-user-context.ts`:
- Around line 105-108: Update the comment immediately above the catch in the MCP
user-context role-loading flow to describe the current fail-closed behavior:
transient D1 failures are rethrown and the request does not fall back to
baseUser. Keep the existing rethrow implementation unchanged.

In `@packages/worker/src/package-retrievers/manifest-cache.ts`:
- Around line 124-141: Update deleteLegacyPackageRetrieverScopeIndexes to return
the actual number of legacy keys deleted rather than
packageRetrieverScopes.length; use the KV delete results to count successful
deletions. Ensure deleteAllPackageRetrieverCacheEntriesForUser adds this actual
count to keys.size so already-clean accounts report zero deleted keys.

---

Outside diff comments:
In `@packages/worker/src/community/avatar.ts`:
- Around line 223-251: Update deleteUserAvatar to perform the account
validation, avatar lookup, database update, and asset deletion within
withAccountWriteLease, matching saveUserAvatar’s lease-protected flow. Guard the
UPDATE with deleting_at IS NULL and verify its meta.changes result so deletion
cannot race with account cleanup or proceed after the account enters deletion.
🪄 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 Plus

Run ID: a70ae9f3-4259-424f-add6-a8bb676dde03

📥 Commits

Reviewing files that changed from the base of the PR and between 11aeab1 and 9a6b2e7.

📒 Files selected for processing (41)
  • docs/contributing/architecture/data-storage.md
  • docs/use/privacy.md
  • packages/worker/migrations/0084-account-deleting-state.sql
  • packages/worker/migrations/0085-account-write-leases.sql
  • packages/worker/src/app/account-data-targets.node.test.ts
  • packages/worker/src/app/account-data-targets.ts
  • packages/worker/src/app/account-deletion-state.ts
  • packages/worker/src/app/account-deletion.node.test.ts
  • packages/worker/src/app/account-deletion.ts
  • packages/worker/src/app/account-export.node.test.ts
  • packages/worker/src/app/account-export.ts
  • packages/worker/src/app/account-r2-export.ts
  • packages/worker/src/app/account-r2-inventory.ts
  • packages/worker/src/app/account-r2-prefix-cleanup.node.test.ts
  • packages/worker/src/app/account-r2-prefix-cleanup.ts
  • packages/worker/src/app/account-user-owned-surfaces.node.test.ts
  • packages/worker/src/app/account-user-owned-surfaces.ts
  • packages/worker/src/app/authenticated-user.node.test.ts
  • packages/worker/src/app/authenticated-user.ts
  • packages/worker/src/app/handlers/account-delete.ts
  • packages/worker/src/app/request-auth-cache.ts
  • packages/worker/src/community/avatar.node.test.ts
  • packages/worker/src/community/avatar.ts
  • packages/worker/src/community/community-flow-test-schema.ts
  • packages/worker/src/community/community-icon.node.test.ts
  • packages/worker/src/community/community-icon.ts
  • packages/worker/src/db.ts
  • packages/worker/src/email/inbound.workers.test.ts
  • packages/worker/src/email/outbound.ts
  • packages/worker/src/email/service.ts
  • packages/worker/src/entitlements/test-schema.ts
  • packages/worker/src/jobs/service.ts
  • packages/worker/src/mcp-auth-user-context.node.test.ts
  • packages/worker/src/mcp-auth-user-context.ts
  • packages/worker/src/package-invocations/http.ts
  • packages/worker/src/package-invocations/http.workers.test.ts
  • packages/worker/src/package-invocations/idempotent-module-invocation.ts
  • packages/worker/src/package-invocations/service.node.test.ts
  • packages/worker/src/package-registry/test-schema.ts
  • packages/worker/src/package-retrievers/manifest-cache.node.test.ts
  • packages/worker/src/package-retrievers/manifest-cache.ts

Comment thread packages/worker/src/app/account-deletion.ts Outdated
Comment thread packages/worker/src/email/service.ts
Comment thread packages/worker/src/mcp-auth-user-context.ts
Comment thread packages/worker/src/package-retrievers/manifest-cache.ts
Comment thread packages/worker/src/app/account-deletion-state.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/worker/src/community/avatar.ts (1)

167-200: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Scope the avatar row reads and writes by stableUserId.

Both paths lease and namespace R2 data using input.stableUserId, but select/update users using only input.numericUserId. If those IDs ever disagree, this can overwrite and delete another user’s avatar while holding a lease for the wrong account. Add stable_user_id = ? to both SELECT and UPDATE predicates and bind input.stableUserId.

Proposed fix
- `SELECT avatar_key FROM users WHERE id = ?`
+ `SELECT avatar_key FROM users WHERE id = ? AND stable_user_id = ?`

- `WHERE id = ? AND deleting_at IS NULL`
+ `WHERE id = ? AND stable_user_id = ? AND deleting_at IS NULL`

As per coding guidelines, “every read/write path must be scoped by userId” and “Cross-user data sharing is a bug.”

Also applies to: 233-251

🤖 Prompt for 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.

In `@packages/worker/src/community/avatar.ts` around lines 167 - 200, Scope the
avatar database operations in the avatar update flow by stableUserId: update the
existing avatar_key SELECT and the users UPDATE predicate to require
stable_user_id = ?, and bind input.stableUserId alongside the existing numeric
ID. Apply the same stable-user constraint to the later cleanup-related users
read/write path around the corresponding avatar handling logic.

Source: Coding guidelines

🤖 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.

Outside diff comments:
In `@packages/worker/src/community/avatar.ts`:
- Around line 167-200: Scope the avatar database operations in the avatar update
flow by stableUserId: update the existing avatar_key SELECT and the users UPDATE
predicate to require stable_user_id = ?, and bind input.stableUserId alongside
the existing numeric ID. Apply the same stable-user constraint to the later
cleanup-related users read/write path around the corresponding avatar handling
logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 04b3b82f-0da3-4871-9f34-112a345f11a2

📥 Commits

Reviewing files that changed from the base of the PR and between 9a6b2e7 and e24fa85.

📒 Files selected for processing (6)
  • packages/worker/src/app/account-deletion.node.test.ts
  • packages/worker/src/app/account-deletion.ts
  • packages/worker/src/community/avatar.ts
  • packages/worker/src/email/service.ts
  • packages/worker/src/mcp-auth-user-context.ts
  • packages/worker/src/package-retrievers/manifest-cache.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/worker/src/mcp-auth-user-context.ts
  • packages/worker/src/package-retrievers/manifest-cache.ts
  • packages/worker/src/app/account-deletion.ts
  • packages/worker/src/app/account-deletion.node.test.ts

Comment thread packages/worker/src/app/account-export.ts
Comment thread packages/worker/src/app/account-deletion.ts Outdated
Comment thread packages/worker/src/app/account-deletion.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/worker/src/jobs/service.ts (1)

1271-1273: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Avoid re-acquiring the write lease inside the leased scheduler paths.

executeJobOnce calls withAccountWriteLease, but it is only invoked from runJobNow and runDueJobsForUser, both of which already acquire a write lease before calling it. Add an internal un-leased executor helper for these callers and keep the leased wrapper only for standalone job execution to avoid an extra lease acquire/heartbeat/release round-trip per due job.

🤖 Prompt for 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.

In `@packages/worker/src/jobs/service.ts` around lines 1271 - 1273, Refactor
executeJobOnce to separate the job execution logic into an internal un-leased
helper, then have runJobNow and runDueJobsForUser call that helper while
retaining their existing leases. Keep executeJobOnce as the leased wrapper for
standalone execution, acquiring the lease before delegating to the helper.
🤖 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.

Nitpick comments:
In `@packages/worker/src/jobs/service.ts`:
- Around line 1271-1273: Refactor executeJobOnce to separate the job execution
logic into an internal un-leased helper, then have runJobNow and
runDueJobsForUser call that helper while retaining their existing leases. Keep
executeJobOnce as the leased wrapper for standalone execution, acquiring the
lease before delegating to the helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: edc5982e-abb3-407d-8ed3-a1168954a534

📥 Commits

Reviewing files that changed from the base of the PR and between e24fa85 and 40e22ca.

📒 Files selected for processing (15)
  • packages/worker/src/app/account-deletion-state.ts
  • packages/worker/src/app/account-deletion.node.test.ts
  • packages/worker/src/app/account-deletion.ts
  • packages/worker/src/app/account-export.node.test.ts
  • packages/worker/src/app/account-export.ts
  • packages/worker/src/community/avatar.ts
  • packages/worker/src/jobs/service.node.test.ts
  • packages/worker/src/jobs/service.ts
  • packages/worker/src/mcp/memory/service.node.test.ts
  • packages/worker/src/mcp/memory/service.ts
  • packages/worker/src/mcp/run-kody-registry.node.test.ts
  • packages/worker/src/package-registry/service.node.test.ts
  • packages/worker/src/package-registry/service.ts
  • packages/worker/src/package-retrievers/manifest-cache.node.test.ts
  • packages/worker/src/package-retrievers/manifest-cache.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/worker/src/app/account-deletion-state.ts
  • packages/worker/src/package-retrievers/manifest-cache.ts
  • packages/worker/src/community/avatar.ts
  • packages/worker/src/app/account-deletion.ts
  • packages/worker/src/app/account-export.node.test.ts
  • packages/worker/src/app/account-export.ts
  • packages/worker/src/app/account-deletion.node.test.ts

@cursor
cursor Bot force-pushed the cursor/review-fix-account-lifecycle-aeba branch from 40e22ca to e78e4cf Compare July 23, 2026 04:02
Comment thread packages/worker/src/app/account-deletion-state.ts
Comment thread packages/worker/src/mcp/session-registry.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/worker/src/package-retrievers/manifest-cache.ts (2)

461-496: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

deletePrefix per-key deletes could approach the KV subrequest ceiling for large accounts.

Each page from kv.list (up to ~1000 keys) is deleted via Promise.all of individual kv.delete() calls, plus the list() call itself, all counting toward the platform's ~1,000 external-service-operations-per-invocation limit. Workers KV bindings don't support bulk delete (only the REST API does), so this is the best available approach with the binding — but for a user with many packages × retrievers × 2 scopes, a single invocation could exceed the cap and throw mid-sweep.

The caller (deleteRetrieverCache in account-deletion.ts) catches this and logs a warning, returning 0, so it won't crash account deletion — but it will leave that user's retriever KV entries orphaned instead of cleaned up, which cuts against this PR's stated goal of atomic/complete cleanup.

Given this is a platform-level constraint and only bites at scale, consider capping kv.list({ prefix, cursor, limit }) to a safer batch size relative to remaining subrequest budget, or explicitly documenting this as a known best-effort limit.

🤖 Prompt for 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.

In `@packages/worker/src/package-retrievers/manifest-cache.ts` around lines 461 -
496, Update deletePrefix within deleteAllPackageRetrieverCacheEntriesForUser to
use a conservative kv.list page limit so each sweep stays within the Workers KV
subrequest budget, while preserving cursor-based pagination and deletion counts.
Document that cleanup is best-effort and may stop or require retry for users
whose key volume exceeds the invocation limit.

22-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive packageRetrieverScopes from the shared schema.

packageRetrieverScopeValues is already exported from #worker/package-registry/types.ts with the same ['search', 'context'] values. Having this module duplicate the full scope list creates drift risk; new scopes added to the shared source won’t automatically be included in cleanup/refresh loops.

♻️ Suggested fix
-const packageRetrieverScopes = [
-	'search',
-	'context',
-] satisfies Array<PackageRetrieverScope>
+const packageRetrieverScopes = packageRetrieverScopeValues

Import packageRetrieverScopeValues from ../package-registry/types.ts; PackageRetrieverScope already maps to this source.

🤖 Prompt for 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.

In `@packages/worker/src/package-retrievers/manifest-cache.ts` around lines 22 -
26, Update packageRetrieverScopes in manifest-cache.ts to derive its values from
the shared packageRetrieverScopeValues export in package-registry/types.ts,
importing that symbol and removing the duplicated literal array. Preserve the
existing PackageRetrieverScope typing and ensure cleanup/refresh loops use the
shared source.
🤖 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 `@packages/worker/src/mcp-auth.workers.test.ts`:
- Around line 118-123: Update the mock database client’s async run() method to
accept the lease-update query only when it includes the stable_user_id-scoped
WHERE predicate and the bound user ID matches the expected test user. Reject
unscoped or mismatched updates instead of accepting every query beginning with
“update users”.

In `@packages/worker/src/mcp/session-registry.ts`:
- Around line 12-17: Update the session insert/upsert flow in the session
registry to reject an existing do_id when its stored user_id differs from
input.userId, rather than overwriting ownership; preserve idempotent behavior
for the same owner. Add a regression test covering a conflicting do_id from
another user and assert the original owner remains unchanged.

---

Nitpick comments:
In `@packages/worker/src/package-retrievers/manifest-cache.ts`:
- Around line 461-496: Update deletePrefix within
deleteAllPackageRetrieverCacheEntriesForUser to use a conservative kv.list page
limit so each sweep stays within the Workers KV subrequest budget, while
preserving cursor-based pagination and deletion counts. Document that cleanup is
best-effort and may stop or require retry for users whose key volume exceeds the
invocation limit.
- Around line 22-26: Update packageRetrieverScopes in manifest-cache.ts to
derive its values from the shared packageRetrieverScopeValues export in
package-registry/types.ts, importing that symbol and removing the duplicated
literal array. Preserve the existing PackageRetrieverScope typing and ensure
cleanup/refresh loops use the shared source.
🪄 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 Plus

Run ID: a2390574-f3db-4aa6-b828-925c9cac5bef

📥 Commits

Reviewing files that changed from the base of the PR and between 40e22ca and e78e4cf.

📒 Files selected for processing (56)
  • docs/contributing/architecture/data-storage.md
  • docs/use/privacy.md
  • packages/worker/migrations/0084-account-deleting-state.sql
  • packages/worker/migrations/0085-account-write-leases.sql
  • packages/worker/migrations/0086-mcp-agent-sessions.sql
  • packages/worker/src/app/account-data-targets.node.test.ts
  • packages/worker/src/app/account-data-targets.ts
  • packages/worker/src/app/account-deletion-state.ts
  • packages/worker/src/app/account-deletion.node.test.ts
  • packages/worker/src/app/account-deletion.ts
  • packages/worker/src/app/account-export.node.test.ts
  • packages/worker/src/app/account-export.ts
  • packages/worker/src/app/account-r2-export.ts
  • packages/worker/src/app/account-r2-inventory.ts
  • packages/worker/src/app/account-r2-prefix-cleanup.node.test.ts
  • packages/worker/src/app/account-r2-prefix-cleanup.ts
  • packages/worker/src/app/account-user-owned-surfaces.node.test.ts
  • packages/worker/src/app/account-user-owned-surfaces.ts
  • packages/worker/src/app/authenticated-user.node.test.ts
  • packages/worker/src/app/authenticated-user.ts
  • packages/worker/src/app/handlers/account-delete.ts
  • packages/worker/src/app/handlers/account-export.ts
  • packages/worker/src/app/request-auth-cache.ts
  • packages/worker/src/community/avatar.node.test.ts
  • packages/worker/src/community/avatar.ts
  • packages/worker/src/community/community-flow-test-schema.ts
  • packages/worker/src/community/community-icon.node.test.ts
  • packages/worker/src/community/community-icon.ts
  • packages/worker/src/db.ts
  • packages/worker/src/email/inbound.workers.test.ts
  • packages/worker/src/email/outbound.ts
  • packages/worker/src/email/service.ts
  • packages/worker/src/entitlements/test-schema.ts
  • packages/worker/src/jobs/service.node.test.ts
  • packages/worker/src/jobs/service.ts
  • packages/worker/src/mcp-auth-user-context.node.test.ts
  • packages/worker/src/mcp-auth-user-context.ts
  • packages/worker/src/mcp-auth.ts
  • packages/worker/src/mcp-auth.workers.test.ts
  • packages/worker/src/mcp/index.ts
  • packages/worker/src/mcp/memory/service.node.test.ts
  • packages/worker/src/mcp/memory/service.ts
  • packages/worker/src/mcp/run-kody-registry.node.test.ts
  • packages/worker/src/mcp/session-registry.node.test.ts
  • packages/worker/src/mcp/session-registry.ts
  • packages/worker/src/package-invocations/http.ts
  • packages/worker/src/package-invocations/http.workers.test.ts
  • packages/worker/src/package-invocations/idempotent-module-invocation.ts
  • packages/worker/src/package-invocations/service.node.test.ts
  • packages/worker/src/package-registry/service.node.test.ts
  • packages/worker/src/package-registry/service.ts
  • packages/worker/src/package-registry/test-schema.ts
  • packages/worker/src/package-retrievers/manifest-cache.node.test.ts
  • packages/worker/src/package-retrievers/manifest-cache.ts
  • tools/check-migrations.node.test.ts
  • tools/migration-ledger.json
🚧 Files skipped from review as they are similar to previous changes (35)
  • packages/worker/migrations/0085-account-write-leases.sql
  • packages/worker/src/entitlements/test-schema.ts
  • packages/worker/src/db.ts
  • packages/worker/src/mcp/memory/service.node.test.ts
  • packages/worker/src/app/account-r2-prefix-cleanup.ts
  • packages/worker/src/package-invocations/service.node.test.ts
  • packages/worker/src/app/account-data-targets.node.test.ts
  • packages/worker/src/mcp/run-kody-registry.node.test.ts
  • packages/worker/src/package-registry/test-schema.ts
  • packages/worker/src/app/handlers/account-delete.ts
  • packages/worker/src/app/authenticated-user.ts
  • packages/worker/src/jobs/service.node.test.ts
  • packages/worker/src/package-invocations/http.workers.test.ts
  • packages/worker/src/community/community-icon.ts
  • packages/worker/src/email/service.ts
  • packages/worker/src/app/request-auth-cache.ts
  • packages/worker/src/app/account-r2-prefix-cleanup.node.test.ts
  • packages/worker/src/package-invocations/http.ts
  • packages/worker/src/mcp-auth-user-context.node.test.ts
  • packages/worker/src/app/authenticated-user.node.test.ts
  • packages/worker/src/app/account-r2-inventory.ts
  • packages/worker/src/email/inbound.workers.test.ts
  • packages/worker/src/app/account-deletion-state.ts
  • packages/worker/src/app/account-user-owned-surfaces.node.test.ts
  • packages/worker/src/community/community-icon.node.test.ts
  • packages/worker/src/package-retrievers/manifest-cache.node.test.ts
  • packages/worker/src/app/account-r2-export.ts
  • packages/worker/src/jobs/service.ts
  • packages/worker/src/app/account-data-targets.ts
  • packages/worker/src/mcp/memory/service.ts
  • packages/worker/src/package-invocations/idempotent-module-invocation.ts
  • packages/worker/src/app/account-export.node.test.ts
  • packages/worker/src/app/account-deletion.ts
  • packages/worker/src/app/account-deletion.node.test.ts
  • packages/worker/src/community/community-flow-test-schema.ts

Comment thread packages/worker/src/mcp-auth.workers.test.ts
Comment thread packages/worker/src/mcp/session-registry.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/worker/src/mcp/session-registry.ts`:
- Around line 18-21: Update the ownership lookup in the session-registry flow to
filter by both doId and input.userId, binding both values in the query. Treat a
missing matching row as a failed ownership check and preserve the existing
fail-closed behavior; do not read or return another user’s user_id.
🪄 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 Plus

Run ID: 12ed85f0-3765-467e-bc69-695d5826598a

📥 Commits

Reviewing files that changed from the base of the PR and between e78e4cf and 99b682d.

📒 Files selected for processing (3)
  • packages/worker/src/mcp-auth.workers.test.ts
  • packages/worker/src/mcp/session-registry.node.test.ts
  • packages/worker/src/mcp/session-registry.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/worker/src/mcp/session-registry.node.test.ts

Comment thread packages/worker/src/mcp/session-registry.ts Outdated

@cursor cursor Bot left a comment

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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f927e13. Configure here.

Comment thread packages/worker/src/app/account-export.ts Outdated
@kody-bot
kody-bot merged commit d38cac5 into main Jul 23, 2026
5 checks passed
@kody-bot
kody-bot deleted the cursor/review-fix-account-lifecycle-aeba branch July 23, 2026 06:20
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.

3 participants