fix(server,web): renaming a grouped project to its repository name sticks#23074
Problem
Renaming a grouped project (one with more than one checkout) to exactly its repository name doesn't stick. Settings and the sidebar snap back to owner/repo. Fixes #15049.
There are two causes. deriveProjectGroupLabel treats a shared title equal to the repository name as a folder default and shows the repository display path instead. And when every checkout's folder title already equals the new name, projectGroupTitleNeedsUpdate skips the save entirely, so nothing reaches the server.
Change
A folder default and a deliberate rename can have the same text, so the client can't tell them apart without help. The server now records whether a title was set by hand.
- Server:
projection_projectsgainshas_custom_title(migration 068, default0).ProjectStoresets it whenever aproject.meta-updatedevent carries atitle. Only explicit renames do that: settings, mobile, and thet3_project_updateMCP tool.project.createdleaves it false. The flag is derived from existing events, so replay stays consistent and no new event field is needed. - Contracts:
OrchestrationProjectShell.hasCustomTitleis optional, so older servers and cached shells decode as "not renamed", the current behavior. Project shells are sent whole on every update, so web, desktop, mobile, and relay clients get the flag with no other wiring. - Label (
client-runtime, shared by web and mobile): a shared title wins whenever any member hashasCustomTitle. Otherwise the existing rule applies unchanged, so groups nobody renamed still showowner/repo. - Settings save check (web): an edited name is saved unless every member already has that title and the flag. Untouched blurs are still skipped. Mobile already saves whenever the typed name differs from the label shown, so it needed no change.
I didn't backfill the flag for projects renamed before this change. A previous rename to exactly the repository name never reached the server (it hit the skipped save), so a backfill would recover nothing. Other past renames already display correctly.
Scope and approval
Fixes #15049, which a maintainer triaged and confirmed: triage comment. It states the intended behavior: an explicit rename to the plain repository name should stick, while a folder-default title keeps showing owner/repo.
This replaces #15212, which was closed for its model. Julius's review there asked that the default for never-renamed groups be preserved rather than changed. That's why this uses a stored flag instead of dropping the repository-name check.
Verification
Focused tests, run on unfixed code first and failing for the expected reasons, then passing with the fix:
projectGrouping.test.ts: two checkouts titledt3codeshowT3 Code(the repository display name) by default andt3codeonce renamed (fails onmain:expected 'T3 Code' to be 't3code').ProjectStore.test.ts:hasCustomTitlestays false after create and a non-title update, and turns true after a title update (fails onmain:expected undefined to equal false).ProjectSettingsPanel.logic.test.ts: renaming to a title every member already has still saves when the flag is missing, and is skipped once every member has it.
vp run check:changed passes after rebasing onto main: format, lint, knip, typecheck of 29 packages, and 9 test files, including the migration manifest test.
Manual, in an isolated dev server (vp run dev --no-seed, worktree-local state). The fixture is two checkouts in folders named demo, both with origin = https://github.com/acme/demo.git, so each folder title equals the repository name. In Settings → Project, I set Name to demo and blurred the field, then reloaded the page.
Before, on unfixed code: the header stays acme/demo while the field shows demo, and after a reload the field snaps back to acme/demo.
| Renamed | After reload |
|---|---|
| before, renamed | before, after reload |
After, on this branch, with the same database upgraded in place by migration 068: before the rename the untouched group still reads acme/demo, so the default is preserved. After the rename, the header and field both read demo, and they still do after a reload. The projection shows has_custom_title = 1 on both demo checkouts and 0 on an untouched project.
| Renamed | After reload |
|---|---|
| after, renamed | after, after reload |
Not checked: a native mobile run (mobile uses the same label helper and is covered by the shared tests), and two separate environments (I used two checkouts in one environment, which takes the same grouping path).
Built with Claude Opus 5.5 via Claude Code, running in T3 Code.
Checks
Added 1 commit
- fix(server,web): renaming a grouped project to its repository name sticks2970a67
Approvability
Verdict: Approved at 2970a67
Macroscope's review found this PR approvable — This is a focused fix for grouped project renames that adds a backward-compatible persisted marker and preserves the existing repository-name default for untouched projects. Runtime changes are localized to project persistence, shell transport, grouping-label derivation, and the web save check, with targeted tests added.
You can add or adjust custom eligibility rules. Learn more.
No actionable comments were generated in the recent review. 🎉
ℹ️ Recent review info
⚙️ Run configuration
- Configuration used: Path: .coderabbit.config.ts
- Review profile: CHILL
- Plan: Advanced
- Run ID:
f304f616-67f2-44f1-b30f-38f4723a31f4
📥 Commits
Reviewing files that changed from the base of the PR and between d13fc846700e5bb7d63e1bfaabb1e55733912b21 and 2970a672b898961becccb7ddbb72587a2897fa5a.
📒 Files selected for processing: 15
apps/server/src/orchestration-v2/ProjectCommands.test.tsapps/server/src/orchestration-v2/ProjectStore.test.tsapps/server/src/orchestration-v2/ProjectStore.tsapps/server/src/orchestration-v2/RuntimePolicy.test.tsapps/server/src/orchestration-v2/ThreadLaunchService.test.tsapps/server/src/orchestration-v2/ThreadTitleRegenerationService.test.tsapps/server/src/persistence/Migrations.tsapps/server/src/persistence/Migrations/068_ProjectionProjectsCustomTitle.tsapps/server/src/persistence/reconcileV2PreviewMigration.test.tsapps/web/src/components/settings/ProjectSettingsPanel.logic.test.tsapps/web/src/components/settings/ProjectSettingsPanel.logic.tsapps/web/src/components/settings/ProjectSettingsPanel.tsxpackages/client-runtime/src/state/projectGrouping.test.tspackages/client-runtime/src/state/projectGrouping.tspackages/contracts/src/orchestrationProject.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.
📝 Walkthrough
The change adds persisted custom-title state to project shells. Settings group renames and repository-group labels now use that state to distinguish explicitly selected titles from default titles.
Changes: Custom title tracking
Layer / File(s) Summary Persist custom-title state
packages/contracts/src/orchestrationProject.ts,apps/server/src/persistence/Migrations/068_ProjectionProjectsCustomTitle.ts,apps/server/src/persistence/Migrations.ts,apps/server/src/orchestration-v2/ProjectStore.ts,apps/server/src/orchestration-v2/ProjectStore.test.ts,apps/server/src/orchestration-v2/ProjectCommands.test.ts,apps/server/src/orchestration-v2/RuntimePolicy.test.ts,apps/server/src/orchestration-v2/ThreadLaunchService.test.ts,apps/server/src/orchestration-v2/ThreadTitleRegenerationService.test.ts,apps/server/src/persistence/reconcileV2PreviewMigration.test.tsProject shells and the database now track whether a title is custom. New projects start with the flag set to false, and title updates set it to true. Migration and tests cover the field and its persistence. Use custom-title state when saving group renames
apps/web/src/components/settings/ProjectSettingsPanel.logic.ts,apps/web/src/components/settings/ProjectSettingsPanel.tsx,apps/web/src/components/settings/ProjectSettingsPanel.logic.test.tsGroup rename checks now use each member’s title and custom-title state. Tests cover renaming to a default title and avoiding updates when the custom title is already applied. Derive labels from custom-title state
packages/client-runtime/src/state/projectGrouping.ts,packages/client-runtime/src/state/projectGrouping.test.tsRepository-group labels now use a shared title when any member has a custom title, including when that title matches the repository name or display name. Priority: ⬇️ Low
Estimated code review effort: 3 (Moderate) | ~20 minutes
Change: Bug fix · Severity of issue fixed: Low
Suggested reviewers:
maria-rcks
Merge Risk: ⚪ Minimal · up to 2970a
The reviewed change preserves explicit project renames without an identified merge-blocking issue. It is ready to merge after normal checks.
Pre-merge checks: Passed 4
Check name Status Explanation Title check Passed The title clearly describes the primary fix: grouped project renames to the repository name now persist. Description check Passed The description covers the problem, implementation, scope approval, focused tests, broader checks, manual verification, screenshots, and known limitations. Linked Issues check Passed Issue #15049requires a grouped project to retain a rename to its repository name.ProjectStorenow persistshasCustomTitlefrom title updates and exposes it in project shells. `deriveProjectGroup…Out of Scope Changes check Passed The migration, projection field, contract field, client label logic, settings save logic, and regression tests all support the linked rename fix. The server and contract changes deliver the state requ… Full details: Linked Issues check
Explanation
Issue
#15049requires a grouped project to retain a rename to its repository name.ProjectStorenow persistshasCustomTitlefrom title updates and exposes it in project shells.deriveProjectGroupLabeluses the shared title when a member has a custom title.projectGroupTitleNeedsUpdatesaves an edited title when a member lacks the custom-title flag. The added grouping, store, and settings tests cover these behaviors. The existing default label rule remains for groups without a custom title.
Full details: Out of Scope Changes check
Explanation
The migration, projection field, contract field, client label logic, settings save logic, and regression tests all support the linked rename fix. The server and contract changes deliver the state required by web and shared client code. No unrelated change is demonstrated by the reviewed summary or focused diffs.
✨ Finishing Touches
🧪 Generate unit tests (beta)
- Create a new PR
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.