Skip to content

Update package scopes when username changes - #793

Merged
kody-bot merged 2 commits into
mainfrom
cursor/username-change-packages-28f8
Jul 20, 2026
Merged

kody-bot merged 2 commits into
mainfrom
cursor/username-change-packages-28f8

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Jul 20, 2026 •

Copy link
Copy Markdown
Owner

Summary

Username changes previously only updated users.username. Saved packages kept the old @{username}/{kodyId} name, so later saves/publishes failed scope checks and community “by @…” stayed stale.

This PR cascades username changes across packages:

  • Claim the new username first (unique constraint wins races)
  • Rewrite package.json#name and same-account @old/ references (kody.dependencies, emits/subscriptions topic keys, kody:@ imports)
  • Publish an automatic update commit per package with the full rewritten tree
  • Roll back the username (and compensate packages) if the cascade fails
  • Warn on /account that package commits and third-party / dynamic invocations may be affected
  • Auto-republish community listings that were already pinned to the package’s latest commit
  • Document the behavior in usage docs

Test plan

  • Unit tests for scope rewrite helpers
  • Unit tests for package cascade + community republish wiring
  • Account profile API tests for success, package-failure abort, and duplicate username
  • Manual: change username on an account with packages + an up-to-date community listing; confirm package names, commit message, listing name, and invocation URLs
System recap — extends existing primitives (medium risk)

Mode: recap · Base: main @ 6ed468bc · Head: 215c34ca

Classification: extends — username change now rewrites and republishes saved packages (and may republish community listings).

Primitives touched

Primitive Group Impact
saved-packages assistant extends — username rename rewrites scope and publishes update commits
app-ui surfaces extends — account profile warns and reports package/community outcomes
repo-sessions runtime extends — sync/publish accept expected scope + commit message for rename commits
community-listings assistant composes — republish listings already on latest commit after rename

System map

Username save claims users.username first, then rewrites package scopes and republishes eligible community listings.

flowchart TD
  A["Account /profile.json<br/>POST username"] --> B["Claim users.username"]
  B --> C["updatePackagesForUsernameChange"]
  C --> D["Rewrite @old → @new<br/>full file tree"]
  D --> E["syncArtifactSourceSnapshot<br/>force publish + commit"]
  E --> F["refreshSavedPackageProjection<br/>best-effort"]
  F --> G{"Listing was on<br/>latest commit?"}
  G -->|yes| H["publishCommunityListing"]
  G -->|no| I["Leave listing pin"]
  C -.->|failure| J["Roll back username<br/>compensate packages"]

  style B fill:#f4c27a,stroke:#8a5a00,color:#000
  style D fill:#f4c27a,stroke:#8a5a00,color:#000
  style E fill:#f4c27a,stroke:#8a5a00,color:#000
  style H fill:#9fd89f,stroke:#2f6b2f,color:#000
  style A fill:#9fd89f,stroke:#2f6b2f,color:#000
Loading

Legend: green = composes · amber = extended by this PR

Invariants

  • Per-user isolation preserved: only the signed-in user’s packages/listings are rewritten.
  • Username is claimed before package publishes; cascade failure rolls username back and compensates packages best-effort.
  • Third-party packages that hard-code the old @{username} scope are not rewritten (called out in UI + docs).

Docs

  • docs/use/packages.md — username scope cascade
  • docs/use/community-packages.md — auto-republish when listing was on latest
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features
    • Username changes now rewrite saved package scopes and references, refresh saved package content, and keep pinned community listings current.
    • Profile success messaging now includes package/community update counts and warnings; the UI explains that scope updates and community republishing will happen automatically.
    • Username updates are handled safely: if package updates fail, changes are not applied and an error is shown.
  • Documentation
    • Expanded guidance on community listings and scoped package names, including what changes after a username update and what to update for third-party integrations.

Username changes previously only rewrote users.username, leaving saved
packages and community listings on the old @{username} scope. Cascade
rewrites package.json names and same-account kody:@ references, publishes
an automatic update commit per package before flipping the username, warns
on the account profile form, and republishes community listings that were
already on the latest package commit.

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

coderabbitai Bot commented Jul 20, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Username changes now rewrite saved package scopes and references, publish updated package commits, republish affected community listings, and expose update results and warnings through the account API and client.

Changes

Username Scope Rename

Layer / File(s) Summary
Scope rewrite utilities
packages/worker/src/package-registry/username-scope-rewrite.ts, packages/worker/src/package-registry/username-scope-rewrite.node.test.ts
Adds normalized scope rewriting, package-name generation, changed-file tracking, and tests for exact references, package metadata, and no-op renames.
Package update and publishing pipeline
packages/worker/src/package-registry/username-change-packages.ts, packages/worker/src/package-registry/username-change-packages.node.test.ts, packages/worker/src/repo/source-sync.ts, packages/worker/src/repo/repo-session-do.ts
Rewrites and republishes saved packages, refreshes projections, compensates partial failures, republishes community listings, and supports custom commit messages and expected scopes.
Account username-change flow
packages/worker/src/app/handlers/account-profile.ts, packages/worker/src/app/handlers/account-profile.node.test.ts, packages/worker/client/routes/account.tsx, docs/use/*.md
Coordinates package updates around username persistence, returns package and community outcomes, displays warnings and explanatory text, and documents scope and listing behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AccountRoute
  participant AccountProfileHandler
  participant PackageUpdateService
  participant Repository
  participant CommunityListings
  User->>AccountRoute: submit username change
  AccountRoute->>AccountProfileHandler: POST profile update
  AccountProfileHandler->>PackageUpdateService: update package scopes
  PackageUpdateService->>Repository: publish rewritten package commits
  AccountProfileHandler->>AccountProfileHandler: persist username
  AccountProfileHandler->>CommunityListings: republish pinned listings
  AccountProfileHandler-->>AccountRoute: return update counts and warnings
  AccountRoute-->>User: show save result
Loading

Possibly related PRs

  • kentcdodds/kody#454: Both touch package-scope enforcement and publishing inputs passed through syncArtifactSourceSnapshot and publishSession.
  • kentcdodds/kody#509: Both modify the publishSession repository publishing flow.
  • kentcdodds/kody#722: Both update account-profile username-change tests and audit assertions.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% 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 clearly summarizes the main change: package scopes are updated when a username changes.
✨ 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/username-change-packages-28f8

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.

@kody-bot
kody-bot marked this pull request as ready for review July 20, 2026 01:59
@github-actions

github-actions Bot commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

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

Worker: kody-pr-793
D1: kody-pr-793-db
KV: kody-pr-793-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 (3)
packages/worker/src/package-registry/username-change-packages.node.test.ts (1)

61-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No test coverage for unpublished saved packages.

Every test here mocks loadPackageSourceBySourceId with published_commit: 'commit-old'. Add a case where published_commit is null to cover the bootstrap-publish path in syncArtifactSourceSnapshot — this is exactly the scenario behind the file-loss issue flagged in username-change-packages.ts, and would catch regressions once fixed.

🤖 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/username-change-packages.node.test.ts`
around lines 61 - 125, Add a test alongside updatePackagesForUsernameChange that
mocks loadPackageSourceBySourceId with source.published_commit set to null, then
verifies the username-change flow preserves and rewrites the package files
through syncArtifactSourceSnapshot during bootstrap publish. Assert the
unpublished package is updated successfully and the snapshot call includes the
expected renamed package.json and source identifiers.
packages/worker/src/app/handlers/account-profile.node.test.ts (1)

300-336: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing coverage for the post-package-update DB-failure compensation branch.

Tests cover the pre-check duplicate-username rejection and the package-update failure, but not the case where db.update itself fails with a unique-constraint violation after packages were already renamed (lines 112-137 of the handler). That path re-invokes updatePackagesForUsernameChange with swapped scopes to compensate — worth a dedicated test (e.g. mock db.update to throw a constraint error and assert the compensating call args).

🤖 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/app/handlers/account-profile.node.test.ts` around lines
300 - 336, Add a dedicated test for the post-package-update database failure
path in the account profile API handler: make the username package update
succeed, mock the user DB update to throw a unique-constraint error, then assert
the response and that updatePackagesForUsernameChange is called again with
swapped old/new scopes to compensate. Keep assertions focused on the
compensation arguments and failure behavior, using the existing test helpers and
mocks.
packages/worker/src/repo/source-sync.ts (1)

26-33: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

expectedPackageScope/commitMessage aren't honored on the bootstrap path.

These two new fields are only forwarded into session.publishSession (used when source.published_commit already exists). The "never published" bootstrap branch ignores both, so renaming an unpublished package's scope publishes without scope validation and with a generic Bootstrap source repo … message instead of the rename-specific one. Given the sibling data-loss issue flagged in username-change-packages.ts confirms this branch is reachable during rename, consider threading both fields through to bootstrapSource for consistency.

🤖 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/source-sync.ts` around lines 26 - 33, The bootstrap
path must honor both options. Update the caller and `bootstrapSource` flow to
accept and forward `expectedPackageScope` and `commitMessage`, applying them to
bootstrap publish validation and commit-message generation instead of the
generic defaults, while preserving existing behavior when they are absent.
🤖 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/handlers/account-profile.ts`:
- Around line 82-137: Reserve the requested username atomically before calling
updatePackagesForUsernameChange, using the existing database transaction/locking
mechanism so concurrent requests cannot pass the availability check
simultaneously. Only publish package rewrites after the reservation succeeds,
and finalize or release the reservation consistently when the username update
succeeds or fails; keep the package compensation path for failures after
publication.

In `@packages/worker/src/package-registry/username-change-packages.ts`:
- Around line 35-46: Update the package rename flow around changedFilesOnly and
syncArtifactSourceSnapshot to pass the complete rewritten file set, not only
rewrite.changedPaths. Preserve the rewritten contents for every file and ensure
both unpublished bootstrap and incremental sync paths receive the full tree
without dropping unchanged files.
- Around line 83-100: Make the refreshSavedPackageProjection call in
rewriteAndPublishPackageScope best-effort by catching and handling refresh
failures without rejecting the function after the publish succeeds. Ensure the
function still returns its normal result so the package is added to applied and
remains eligible for compensation, while preserving successful projection
refresh behavior.

---

Nitpick comments:
In `@packages/worker/src/app/handlers/account-profile.node.test.ts`:
- Around line 300-336: Add a dedicated test for the post-package-update database
failure path in the account profile API handler: make the username package
update succeed, mock the user DB update to throw a unique-constraint error, then
assert the response and that updatePackagesForUsernameChange is called again
with swapped old/new scopes to compensate. Keep assertions focused on the
compensation arguments and failure behavior, using the existing test helpers and
mocks.

In `@packages/worker/src/package-registry/username-change-packages.node.test.ts`:
- Around line 61-125: Add a test alongside updatePackagesForUsernameChange that
mocks loadPackageSourceBySourceId with source.published_commit set to null, then
verifies the username-change flow preserves and rewrites the package files
through syncArtifactSourceSnapshot during bootstrap publish. Assert the
unpublished package is updated successfully and the snapshot call includes the
expected renamed package.json and source identifiers.

In `@packages/worker/src/repo/source-sync.ts`:
- Around line 26-33: The bootstrap path must honor both options. Update the
caller and `bootstrapSource` flow to accept and forward `expectedPackageScope`
and `commitMessage`, applying them to bootstrap publish validation and
commit-message generation instead of the generic defaults, while preserving
existing behavior when they are absent.
🪄 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: 83c97597-1e19-4260-8088-17b2cdcdc00c

📥 Commits

Reviewing files that changed from the base of the PR and between 6ed468b and 7b147ea.

📒 Files selected for processing (11)
  • docs/use/community-packages.md
  • docs/use/packages.md
  • packages/worker/client/routes/account.tsx
  • packages/worker/src/app/handlers/account-profile.node.test.ts
  • packages/worker/src/app/handlers/account-profile.ts
  • packages/worker/src/package-registry/username-change-packages.node.test.ts
  • packages/worker/src/package-registry/username-change-packages.ts
  • packages/worker/src/package-registry/username-scope-rewrite.node.test.ts
  • packages/worker/src/package-registry/username-scope-rewrite.ts
  • packages/worker/src/repo/repo-session-do.ts
  • packages/worker/src/repo/source-sync.ts

Comment thread packages/worker/src/app/handlers/account-profile.ts Outdated
Comment thread packages/worker/src/package-registry/username-change-packages.ts Outdated
Comment thread packages/worker/src/package-registry/username-change-packages.ts Outdated
Claim the username before publishing package rewrites so concurrent
renames lose on the unique constraint, sync the full rewritten file tree
to avoid bootstrap data loss, and keep projection refresh best-effort so
published packages stay eligible for compensation.

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

@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 3 potential issues.

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 215c34c. Configure here.

error: getErrorMessage(error),
}),
)
}

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.

Stale listing name after refresh

Medium Severity

After a scope rewrite publish, refreshSavedPackageProjection failures are logged and ignored, but the account handler still auto-republishes community listings. publishCommunityListing sets listing name from the saved-package row, so a failed projection can leave the public listing on the old @{username}/… name even though the published source already uses the new scope.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 215c34c. Configure here.

}),
)
}
}

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.

Failed compensation leaves wrong scopes

High Severity

When a later package fails during the username cascade, compensation republish errors are only logged. The profile handler still rolls back users.username to the previous value. Packages that were already published under the new scope can remain there while the account username is old again, breaking scope consistency and later publishes.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 215c34c. Configure here.

error: `Username was not changed because package updates failed: ${getErrorMessage(error)}`,
},
500,
)

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.

Rollback failure misreports username

High Severity

On package cascade failure the handler tries to restore the previous username, but if that rollback update fails it only logs the error and still responds that the username was not changed. The database can retain the new username while the client is told the rename was aborted.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 215c34c. Configure here.

@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/app/handlers/account-profile.ts (1)

111-151: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Rollback-failure path leaves the account permanently stuck with no way to retry.

If the package update throws and the compensating db.update back to previousUsername (122-125) also fails, execution still falls through to the same response at 144-150: "Username was not changed because package updates failed" — but the username actually was changed and never reverted.

Worse, this creates a stuck state with no self-heal: on the client's next request with the same desired username, previousUsername = user.username is now read fresh from the DB (already the new value), so the fast path at Line 59 (username === previousUsername) short-circuits and returns success without ever re-attempting updatePackagesForUsernameChange. Packages/community listings remain permanently unrewritten under the old scope while the account shows the new username, and there's no code path left to retry the package sync.

Consider tracking rollback success explicitly and, on rollback failure, returning a distinct response (e.g. a different status/message indicating manual follow-up or a "pending package sync" flag) so the fast path at Line 59 doesn't swallow future retries.

💡 Sketch of a safer fallback
 			} catch (error) {
+				let rollbackSucceeded = true
 				try {
 					await db.update(usersTable, user.userId, {
 						username: previousUsername,
 						updated_at: utcSqliteTimestamp(),
 					})
 				} catch (rollbackError) {
+					rollbackSucceeded = false
 					console.error(
 						JSON.stringify({
 							message: 'username-change rollback failed after package error',
 							userId: packageUserId,
 							error: getErrorMessage(rollbackError),
 						}),
 					)
 				}
 				void logAuditEvent({
 					category: 'account',
 					action: 'update_username',
 					result: 'failure',
 					email: user.email,
 					ip: requestIp,
 					path: url.pathname,
-					reason: 'package_scope_update_failed',
+					reason: rollbackSucceeded
+						? 'package_scope_update_failed'
+						: 'package_scope_update_failed_rollback_failed',
 				})
 				return jsonResponse(
 					{
 						ok: false,
-						error: `Username was not changed because package updates failed: ${getErrorMessage(error)}`,
+						error: rollbackSucceeded
+							? `Username was not changed because package updates failed: ${getErrorMessage(error)}`
+							: `Username was changed to @${username}, but package updates failed and could not be automatically undone: ${getErrorMessage(error)}. Please retry or contact support.`,
 					},
 					500,
 				)
 			}
🤖 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/app/handlers/account-profile.ts` around lines 111 - 151,
Track whether the compensating update in the updatePackagesForUsernameChange
error path succeeds. When rollback fails, return a distinct
pending/manual-follow-up response and ensure the
username-equals-previousUsername fast path does not report success or swallow a
retry while package synchronization remains incomplete; preserve the existing
failure response when rollback succeeds.
🤖 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/app/handlers/account-profile.ts`:
- Around line 111-151: Track whether the compensating update in the
updatePackagesForUsernameChange error path succeeds. When rollback fails, return
a distinct pending/manual-follow-up response and ensure the
username-equals-previousUsername fast path does not report success or swallow a
retry while package synchronization remains incomplete; preserve the existing
failure response when rollback succeeds.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8f605099-aca7-4b73-810d-c524fe7c83f4

📥 Commits

Reviewing files that changed from the base of the PR and between 7b147ea and 215c34c.

📒 Files selected for processing (3)
  • packages/worker/src/app/handlers/account-profile.ts
  • packages/worker/src/package-registry/username-change-packages.node.test.ts
  • packages/worker/src/package-registry/username-change-packages.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/worker/src/package-registry/username-change-packages.node.test.ts
  • packages/worker/src/package-registry/username-change-packages.ts

@kody-bot
kody-bot merged commit 21a3a7b into main Jul 20, 2026
5 checks passed
@kody-bot
kody-bot deleted the cursor/username-change-packages-28f8 branch July 20, 2026 02: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