Skip to content

Clean up Artifacts repos on package delete and account deletion - #511

Merged
kentcdodds merged 2 commits into
mainfrom
cursor/artifacts-repo-cleanup-c3f3
May 28, 2026
Merged

kentcdodds merged 2 commits into
mainfrom
cursor/artifacts-repo-cleanup-c3f3

Conversation

@kentcdodds

@kentcdodds kentcdodds commented May 28, 2026 •

Copy link
Copy Markdown
Owner

Add DELETE support to the Artifacts REST binding and best-effort cleanup orchestration for entity source repos and session fork repos. Wire cleanup into deleteSavedPackageProjection and deleteUserAccount while preserving existing D1 and KV cascade behavior.


Note

Medium Risk
Destructive, best-effort remote deletes tied to user data removal; failures are warned but D1 cleanup still proceeds, so orphaned Artifacts repos are possible if the API is down or mis-scoped.

Overview
Adds DELETE to the Cloudflare Artifacts REST binding (404 treated as already gone) and a new best-effort cleanup layer that removes entity-source repos and session fork repos for the signed-in user, with user-scope checks and warnings when Artifacts is unavailable.

Package delete now runs cleanupArtifactReposForPackage before stopping services and removing D1/KV/vector projection data; the package_delete capability text reflects that behavior.

Account deletion runs cleanupAllUserArtifactRepos after vector cleanup and before storage/KV/D1 cascades, and reports deletedArtifactRepos on the deletion result.

Supporting changes: listRepoSessionsByUser for enumerating all session repos on full account cleanup, plus unit tests for the binding delete path and cleanup orchestration.

Reviewed by Cursor Bugbot for commit 7da4d5c. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features
    • Artifact repositories associated with user accounts are now automatically cleaned up when accounts are deleted
    • Artifact repositories linked to saved packages are now automatically removed when those packages are deleted

Review Change Stack

Add DELETE support to the Artifacts REST binding and best-effort cleanup
orchestration for entity source repos and session fork repos. Wire cleanup
into deleteSavedPackageProjection and deleteUserAccount while preserving
existing D1 and KV cascade behavior.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@coderabbitai

coderabbitai Bot commented May 28, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR introduces artifact repository cleanup infrastructure. It adds a delete method to the artifacts REST binding, creates a dedicated cleanup module with three entry points (single-repo, package-scoped, and user-scoped), and integrates cleanup calls into account deletion and package deletion workflows. All cleanup operations are best-effort with appropriate access control and warning logging.

Changes

Artifact Repository Cleanup

Layer / File(s) Summary
Artifacts binding delete support
packages/worker/src/repo/artifacts.ts, packages/worker/src/repo/artifacts.node.test.ts, packages/worker/src/repo/repo-sessions.ts
ArtifactNamespaceBinding gains a delete(name) method that issues Cloudflare REST DELETE requests. The REST binding implementation maps 404 responses to alreadyDeleted: true and successful deletes to alreadyDeleted: false with the repo id. Helper listRepoSessionsByUser queries repo sessions by user id, ordered by recency.
Core cleanup module with three entry points and comprehensive tests
packages/worker/src/repo/artifact-repo-cleanup.ts, packages/worker/src/repo/artifact-repo-cleanup.node.test.ts
Implements deleteUserScopedArtifactRepo (single-repo deletion with access gating), cleanupArtifactReposForPackage (loads source, verifies user ownership, deletes source + session repos), and cleanupAllUserArtifactRepos (lists all sources and sessions, deduplicates, deletes all). All operations gate on Artifacts access availability and emit warnings for mismatches or unavailability. Test suite verifies deletion sequencing, deduplication, user scope validation, and access unavailable handling.
Package deletion cleanup integration
packages/worker/src/package-registry/service.ts, packages/worker/src/package-registry/service.node.test.ts
deleteSavedPackageProjection calls cleanupArtifactReposForPackage with the package's sourceId in a try-catch block, logging errors without blocking subsequent deletion steps. Tests mock and verify the cleanup invocation with correct parameters.
Account deletion cleanup integration
packages/worker/src/app/account-deletion.ts
AccountDeletionResult gains deletedArtifactRepos: number counter. deleteUserAccount calls cleanupAllUserArtifactRepos and stores the count in the result.
Capability documentation update
packages/worker/src/mcp/capabilities/packages/delete-package.ts
package_delete capability description updated to state that deleting a saved package projection cleans up associated Artifacts repos.

Sequence Diagram

sequenceDiagram
  participant User
  participant CleanupAPI
  participant ArtifactBinding
  participant Database
  User->>CleanupAPI: cleanupAllUserArtifactRepos(userId)
  CleanupAPI->>Database: list all sources and sessions
  CleanupAPI->>CleanupAPI: deduplicate repo names
  CleanupAPI->>ArtifactBinding: delete each repo
  ArtifactBinding-->>CleanupAPI: deletion status
  CleanupAPI-->>User: total deleted count
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 With cleanup repos and binding delight,
Three entry points keep artifacts tight,
No repos shall linger when users depart,
Account and package—a well-orchestrated start!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% 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
Title check ✅ Passed The PR title directly and clearly describes the main changes: adding cleanup of Artifacts repos during both package deletion and account deletion operations.
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.

✏️ 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 cursor/artifacts-repo-cleanup-c3f3

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.

@github-actions

github-actions Bot commented May 28, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-511.kentcdodds.workers.dev

Worker: kody-pr-511
D1: kody-pr-511-db
KV: kody-pr-511-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: 3

🧹 Nitpick comments (4)
packages/worker/src/package-registry/service.node.test.ts (1)

362-366: ⚡ Quick win

Add a rejection-path test for artifact cleanup.

Please add one test where cleanupArtifactReposForPackage rejects and verify deleteSavedPackage, deleteSavedPackageVector, and syncJobManagerAlarm still run.

🤖 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-registry/service.node.test.ts` around lines 362 -
366, Add a new unit test that stubs mockModule.cleanupArtifactReposForPackage to
return a rejected promise and then invokes the same code path used in the
existing test; verify that despite the rejection cleanupArtifactReposForPackage
was called with { env, userId: 'user-1', sourceId: 'source-1' } and that
deleteSavedPackage, deleteSavedPackageVector, and syncJobManagerAlarm were still
invoked (use toHaveBeenCalled / toHaveBeenCalledWith as in the existing test),
and ensure the test awaits the promise and handles the rejection so it does not
fail the test (e.g., return or await the call and catch the error).
packages/worker/src/package-registry/service.ts (1)

274-288: ⚡ Quick win

Surface non-throw cleanup warnings for manual follow-up.

cleanupArtifactReposForPackage can report skipped/manual-cleanup scenarios through warnings without throwing. Right now, only thrown errors get logged, so those cases are silent.

💡 Proposed change
 	if (savedPackage) {
+		const cleanupWarnings: Array<string> = []
 		await cleanupArtifactReposForPackage({
 			env: input.env,
 			userId: input.userId,
 			sourceId: savedPackage.sourceId,
+			warnings: cleanupWarnings,
 		}).catch((error) => {
 			console.warn(
 				JSON.stringify({
 					message: 'package artifact repo cleanup failed',
 					userId: input.userId,
 					packageId: input.packageId,
 					sourceId: savedPackage.sourceId,
 					error: error instanceof Error ? error.message : String(error),
 				}),
 			)
 		})
+		for (const warning of cleanupWarnings) {
+			console.warn(
+				JSON.stringify({
+					message: 'package artifact repo cleanup warning',
+					userId: input.userId,
+					packageId: input.packageId,
+					sourceId: savedPackage.sourceId,
+					warning,
+				}),
+			)
+		}
 		const listedServices = await listSavedPackageServices({
🤖 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-registry/service.ts` around lines 274 - 288, The
cleanup call currently only logs thrown errors; update the call to capture and
inspect the result of cleanupArtifactReposForPackage (e.g., const result = await
cleanupArtifactReposForPackage({...})) and if result contains a non-empty
warnings array (result.warnings) log those warnings for manual follow-up
including context (userId: input.userId, packageId: input.packageId, sourceId:
savedPackage.sourceId); keep the existing .catch to log thrown errors as before
but add the warnings log path so skipped/manual-cleanup scenarios are not
silent.
packages/worker/src/repo/artifact-repo-cleanup.ts (1)

39-39: ⚡ Quick win

Variable shadowing: use a different name.

The local const repoName shadows input.repoName. While this works, it's a code smell that can lead to confusion. Consider using trimmedRepoName or similar.

♻️ Proposed fix
-	const repoName = input.repoName.trim()
-	if (!repoName) return false
+	const trimmedRepoName = input.repoName.trim()
+	if (!trimmedRepoName) return false
 	if (!hasArtifactsAccess(input.env)) {
 		return false
 	}
 	try {
 		const binding = getArtifactsBinding(input.env)
-		const result = await binding.delete(repoName)
+		const result = await binding.delete(trimmedRepoName)
 		logArtifactRepoDeleted({
 			userId: input.userId,
-			repoName,
+			repoName: trimmedRepoName,
 			result,
 		})
 		return true
 	} catch (error) {
 		const message = error instanceof Error ? error.message : String(error)
 		input.warnings?.push(
-			`Artifact repo delete failed for ${repoName}: ${message}`,
+			`Artifact repo delete failed for ${trimmedRepoName}: ${message}`,
 		)
 		console.warn(
 			JSON.stringify({
 				message: 'artifact repo delete failed',
 				userId: input.userId,
-				repoName,
+				repoName: trimmedRepoName,
 				error: message,
 			}),
 		)
🤖 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/repo/artifact-repo-cleanup.ts` at line 39, The local
constant repoName shadows input.repoName; rename the trimmed local variable to
something non-conflicting (e.g., trimmedRepoName) in artifact-repo-cleanup.ts so
you use trimmedRepoName = input.repoName.trim() wherever repoName is currently
used in this function (adjust any occurrences in functions/methods like the
cleanup logic that reference repoName).
packages/worker/src/repo/artifact-repo-cleanup.node.test.ts (1)

74-106: 💤 Low value

Test name implies ordering guarantee that isn't enforced.

The test name suggests "deletes session fork repos before entity repo," but the implementation only happens to preserve insertion order via Set iteration. While this works in practice (JavaScript Sets maintain insertion order), the test only verifies both deletions occurred, not their sequence.

💡 Consider clarifying the test name
-test('cleanupArtifactReposForPackage deletes session fork repos before entity repo', async () => {
+test('cleanupArtifactReposForPackage deletes both session fork repos and entity repo', async () => {

Or, if ordering is important, add explicit order verification:

expect(mockModule.deleteArtifactRepo.mock.calls[0][0]).toBe('package-pkg-1-abc123')
expect(mockModule.deleteArtifactRepo.mock.calls[1][0]).toBe('package-pkg-1')
🤖 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/repo/artifact-repo-cleanup.node.test.ts` around lines 74
- 106, The test name claims an ordering guarantee for
cleanupArtifactReposForPackage but doesn't assert call order; update the test to
explicitly verify deletion order by asserting mockModule.deleteArtifactRepo
mock.calls sequence (e.g., check mockModule.deleteArtifactRepo.mock.calls[0][0]
is the session repo name and mockModule.deleteArtifactRepo.mock.calls[1][0] is
the entity repo), or alternatively rename the test to remove the "before"
wording if order is not required; reference cleanupArtifactReposForPackage and
mockModule.deleteArtifactRepo when making the change.
🤖 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 413-417: The call to cleanupAllUserArtifactRepos can throw and
currently aborts the entire account deletion; wrap the call in a try/catch so
deletion remains best-effort: call cleanupAllUserArtifactRepos inside try,
assign its return to result.deletedArtifactRepos on success, and in catch push a
warning with the error message into the existing warnings array (and set
result.deletedArtifactRepos to [] or a sensible default) so the flow continues;
refer to the same pattern used elsewhere in this file for guidance (use the
warnings array and not throwing the error).

In `@packages/worker/src/mcp/capabilities/packages/delete-package.ts`:
- Line 13: Update the capability description in delete-package.ts (the string
describing the "Delete a saved package projection..." capability) to state that
repository cleanup is best-effort rather than guaranteed; reword the sentence to
something like "This removes the saved package from discovery and attempts to
clean up associated Artifacts repos on a best-effort basis, which may not
succeed in all cases." Ensure the updated description replaces the existing line
so it no longer overpromises guaranteed cleanup.

In `@packages/worker/src/repo/artifacts.ts`:
- Line 84: The delete method signature references a missing exported type
ArtifactDeleteRepoResult which causes a compile error; add and export a correct
ArtifactDeleteRepoResult type (e.g., a union/object describing deleted vs
not-found outcome) immediately after the existing ArtifactGetRepoResult type in
the same file so the import in artifact-repo-cleanup.ts resolves; ensure the
exported type name exactly matches ArtifactDeleteRepoResult and its shape
matches usages of the delete(name: string): Promise<ArtifactDeleteRepoResult>
method.

---

Nitpick comments:
In `@packages/worker/src/package-registry/service.node.test.ts`:
- Around line 362-366: Add a new unit test that stubs
mockModule.cleanupArtifactReposForPackage to return a rejected promise and then
invokes the same code path used in the existing test; verify that despite the
rejection cleanupArtifactReposForPackage was called with { env, userId:
'user-1', sourceId: 'source-1' } and that deleteSavedPackage,
deleteSavedPackageVector, and syncJobManagerAlarm were still invoked (use
toHaveBeenCalled / toHaveBeenCalledWith as in the existing test), and ensure the
test awaits the promise and handles the rejection so it does not fail the test
(e.g., return or await the call and catch the error).

In `@packages/worker/src/package-registry/service.ts`:
- Around line 274-288: The cleanup call currently only logs thrown errors;
update the call to capture and inspect the result of
cleanupArtifactReposForPackage (e.g., const result = await
cleanupArtifactReposForPackage({...})) and if result contains a non-empty
warnings array (result.warnings) log those warnings for manual follow-up
including context (userId: input.userId, packageId: input.packageId, sourceId:
savedPackage.sourceId); keep the existing .catch to log thrown errors as before
but add the warnings log path so skipped/manual-cleanup scenarios are not
silent.

In `@packages/worker/src/repo/artifact-repo-cleanup.node.test.ts`:
- Around line 74-106: The test name claims an ordering guarantee for
cleanupArtifactReposForPackage but doesn't assert call order; update the test to
explicitly verify deletion order by asserting mockModule.deleteArtifactRepo
mock.calls sequence (e.g., check mockModule.deleteArtifactRepo.mock.calls[0][0]
is the session repo name and mockModule.deleteArtifactRepo.mock.calls[1][0] is
the entity repo), or alternatively rename the test to remove the "before"
wording if order is not required; reference cleanupArtifactReposForPackage and
mockModule.deleteArtifactRepo when making the change.

In `@packages/worker/src/repo/artifact-repo-cleanup.ts`:
- Line 39: The local constant repoName shadows input.repoName; rename the
trimmed local variable to something non-conflicting (e.g., trimmedRepoName) in
artifact-repo-cleanup.ts so you use trimmedRepoName = input.repoName.trim()
wherever repoName is currently used in this function (adjust any occurrences in
functions/methods like the cleanup logic that reference repoName).
🪄 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: 5cb97534-a435-4b35-800d-4bc211df027f

📥 Commits

Reviewing files that changed from the base of the PR and between ded96cf and a3a9ab3.

📒 Files selected for processing (9)
  • packages/worker/src/app/account-deletion.ts
  • packages/worker/src/mcp/capabilities/packages/delete-package.ts
  • packages/worker/src/package-registry/service.node.test.ts
  • packages/worker/src/package-registry/service.ts
  • packages/worker/src/repo/artifact-repo-cleanup.node.test.ts
  • packages/worker/src/repo/artifact-repo-cleanup.ts
  • packages/worker/src/repo/artifacts.node.test.ts
  • packages/worker/src/repo/artifacts.ts
  • packages/worker/src/repo/repo-sessions.ts

Comment thread packages/worker/src/app/account-deletion.ts
Comment thread packages/worker/src/mcp/capabilities/packages/delete-package.ts Outdated
Comment thread packages/worker/src/repo/artifacts.ts

@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 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 a3a9ab3. Configure here.

Comment thread packages/worker/src/repo/artifact-repo-cleanup.ts
Export the delete result type that CI typecheck requires. Wrap account
deletion artifact cleanup in a catch so D1 enumeration failures cannot
abort the cascade, and clarify package_delete is best-effort.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@kentcdodds
kentcdodds merged commit 3628b07 into main May 28, 2026
5 checks passed
@kentcdodds
kentcdodds deleted the cursor/artifacts-repo-cleanup-c3f3 branch May 28, 2026 18:31
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