feat(desktop): v2 AI workspace rename generates title + branch together#3692
Conversation
Post-create rename now makes a single structured-output call returning
{ title, branchName }, applies the title to v2_workspaces.name, and also
renames the git branch (git branch -m in the worktree + updates the
host-local workspaces.branch + cloud v2_workspaces.branch). Replaces the
naive 20-char slice that was producing mid-word truncations like
"New v2 workspaces na". Branch-prefix support for v2 tracked in SUPER-478.
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughReplaces a single-output workspace-naming helper with a new dual-output LLM flow that returns both display title and kebab-case branch name, validates and formats outputs, performs optional local git branch renames (with dedupe and rollback), and patches the cloud workspace and local DB accordingly. Changes
Sequence DiagramsequenceDiagram
participant CreationRouter as Workspace Creation
participant LLMClient as LLM Client
participant GitLocal as Git (Local)
participant CloudAPI as Cloud Workspace API
participant LocalDB as Local Database
CreationRouter->>LLMClient: generateWorkspaceNamesFromPrompt(prompt)
LLMClient->>LLMClient: Load small model & call structured-output
LLMClient->>LLMClient: Zod validate + format { title, branchName }
LLMClient-->>CreationRouter: { title, branchName } or null
alt Title changed
CreationRouter->>CloudAPI: updateNameFromHost({ name, expectedCurrentName })
end
alt Branch changed
CreationRouter->>CreationRouter: Deduplicate branch name (listBranchNames)
CreationRouter->>GitLocal: git branch -m oldName newName
GitLocal-->>CreationRouter: Success/Failure
alt Git rename succeeded
CreationRouter->>CloudAPI: updateNameFromHost({ branch })
alt Cloud API fails
CreationRouter->>GitLocal: git branch -m newName oldName (rollback)
else Cloud API succeeds
CreationRouter->>LocalDB: Update workspaces.branch (persist)
end
else Git rename failed
CreationRouter-->>CreationRouter: Skip cloud/DB update
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Greptile SummaryThis PR replaces two sequential text-generation calls (with naive
Confidence Score: 4/5Safe to merge once the partial-commit window in the branch rename path is addressed One P1 finding: git + local-DB branch rename can succeed while v2_workspaces.branch silently fails to update, leaving cloud state permanently out of sync. The P2 sanitizer-ordering issue is low-probability in practice but worth fixing before the next iteration. packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts (partial-commit window); packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts (sanitizer ordering)
|
| Filename | Overview |
|---|---|
| packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts | Integrates the new structured AI rename; contains a P1 partial-commit window where git + local-DB branch rename can succeed while the cloud v2_workspaces.branch update silently fails |
| packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts | New file; schema and generation logic look solid, but sanitizeBranchCandidate runs after Zod validation so it can't rescue over-length model responses (silent rename no-op) |
| packages/chat/src/server/desktop/title-generation/structured-generation.ts | New generic structured-output helper; dynamic import pattern, safe-parse double-validation, and null propagation all look correct |
| packages/trpc/src/router/v2-workspace/v2-workspace.ts | updateNameFromHost now accepts optional name+branch with a .refine guard; conditional WHERE and patch building are correct |
| packages/chat/src/server/desktop/title-generation/index.ts | Trivial re-export of the new generateObjectFromMessage; no issues |
| packages/chat/src/server/desktop/index.ts | Public-API re-export updated to include generateObjectFromMessage; no issues |
| packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-name.ts | Deleted file; replaced by ai-workspace-names.ts — removal is intentional and clean |
Sequence Diagram
sequenceDiagram
participant WC as workspace-creation.ts
participant LLM as generateWorkspaceNamesFromPrompt
participant Git as git (worktree)
participant LocalDB as workspaces (local DB)
participant Cloud as v2Workspace.updateNameFromHost
WC->>LLM: generateWorkspaceNamesFromPrompt(prompt)
LLM-->>WC: { title, branchName }
WC->>WC: deduplicateBranchName(branchName, freshBranches)
WC->>Git: git branch -m oldBranch deduped
Git-->>WC: success
WC->>LocalDB: UPDATE workspaces SET branch=deduped
LocalDB-->>WC: ok
WC->>Cloud: updateNameFromHost({ id, name?, branch? })
alt mutate succeeds
Cloud-->>WC: updated row
else mutate fails (P1 gap)
Cloud-->>WC: throws
WC->>WC: outer .catch logs warning
Note over Git,Cloud: git + localDB renamed,<br/>v2_workspaces.branch still stale
end
Prompt To Fix All With AI
This is a comment left during a code review.
Path: packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts
Line: 1034-1067
Comment:
**Git/local-DB rename can commit while `v2_workspaces.branch` stays stale**
The `git branch -m` and `ctx.db.update(workspaces)` at lines 1036–1041 run inside their own `try/catch`, so they commit independently. The subsequent `updateNameFromHost.mutate(patch)` at line 1067 can still throw (network error, workspace already deleted, JWT expiry, etc.), and that error is swallowed by the outer `.catch` at line 1069. At that point the local git branch and `workspaces.branch` carry the new name, but `v2_workspaces.branch` remains the original value — leaving the cloud record permanently out of sync with git.
In the old single-step code, a `mutate` failure was a soft skip; nothing had been mutated locally yet. This PR introduces a new partial-commit window that the existing catch path doesn't roll back.
Consider moving the local DB update (`ctx.db.update(workspaces)…`) to after the `mutate` succeeds, or — at minimum — adding a rollback `git branch -m deduped oldBranchName` in the catch block.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts
Line: 86-93
Comment:
**`sanitizeBranchCandidate` is unreachable for over-length model responses**
`generateObjectFromMessage` runs `schema.safeParse(object)` and returns `null` when it fails. Because `workspaceNamesSchema` enforces `.max(BRANCH_NAME_MAX)` (25 chars), any model response where `branchName` is 26+ characters causes `safeParse` to fail and `result` is `null` — so `sanitizeBranchCandidate` (which has `.slice(0, BRANCH_NAME_MAX)`) never executes for those cases. The entire AI rename silently no-ops.
The doc comment acknowledges this for `title`, but the branch name has a much tighter ceiling (25 vs 40 chars), making silent misses more likely in practice. The simplest fix is to apply `sanitizeBranchCandidate` before calling `generateObjectFromMessage`, or use a Zod `.transform()` on the `branchName` field so sanitization happens inside the schema validation step.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "feat(desktop): v2 AI workspace rename ge..." | Re-trigger Greptile
| try { | ||
| const worktreeGit = await ctx.git(worktreePath); | ||
| await worktreeGit.raw(["branch", "-m", oldBranchName, deduped]); | ||
| ctx.db | ||
| .update(workspaces) | ||
| .set({ branch: deduped }) | ||
| .where(eq(workspaces.id, cloudRow.id)) | ||
| .run(); | ||
| nextBranchName = deduped; | ||
| } catch (err) { | ||
| console.warn( | ||
| "[workspaceCreation.create] git branch rename failed", | ||
| err, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| const patch: { | ||
| id: string; | ||
| name?: string; | ||
| branch?: string; | ||
| expectedCurrentName?: string; | ||
| } = { id: cloudRow.id }; | ||
| if (aiNames.title && aiNames.title !== oldWorkspaceName) { | ||
| patch.name = aiNames.title; | ||
| patch.expectedCurrentName = oldWorkspaceName; | ||
| } | ||
| if (nextBranchName !== oldBranchName) { | ||
| patch.branch = nextBranchName; | ||
| } | ||
| if (patch.name === undefined && patch.branch === undefined) { | ||
| return; | ||
| } | ||
| await ctx.api.v2Workspace.updateNameFromHost.mutate(patch); |
There was a problem hiding this comment.
Git/local-DB rename can commit while
v2_workspaces.branch stays stale
The git branch -m and ctx.db.update(workspaces) at lines 1036–1041 run inside their own try/catch, so they commit independently. The subsequent updateNameFromHost.mutate(patch) at line 1067 can still throw (network error, workspace already deleted, JWT expiry, etc.), and that error is swallowed by the outer .catch at line 1069. At that point the local git branch and workspaces.branch carry the new name, but v2_workspaces.branch remains the original value — leaving the cloud record permanently out of sync with git.
In the old single-step code, a mutate failure was a soft skip; nothing had been mutated locally yet. This PR introduces a new partial-commit window that the existing catch path doesn't roll back.
Consider moving the local DB update (ctx.db.update(workspaces)…) to after the mutate succeeds, or — at minimum — adding a rollback git branch -m deduped oldBranchName in the catch block.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts
Line: 1034-1067
Comment:
**Git/local-DB rename can commit while `v2_workspaces.branch` stays stale**
The `git branch -m` and `ctx.db.update(workspaces)` at lines 1036–1041 run inside their own `try/catch`, so they commit independently. The subsequent `updateNameFromHost.mutate(patch)` at line 1067 can still throw (network error, workspace already deleted, JWT expiry, etc.), and that error is swallowed by the outer `.catch` at line 1069. At that point the local git branch and `workspaces.branch` carry the new name, but `v2_workspaces.branch` remains the original value — leaving the cloud record permanently out of sync with git.
In the old single-step code, a `mutate` failure was a soft skip; nothing had been mutated locally yet. This PR introduces a new partial-commit window that the existing catch path doesn't roll back.
Consider moving the local DB update (`ctx.db.update(workspaces)…`) to after the `mutate` succeeds, or — at minimum — adding a rollback `git branch -m deduped oldBranchName` in the catch block.
How can I resolve this? If you propose a fix, please make it concise.| if (!result) return null; | ||
|
|
||
| const branchName = sanitizeBranchCandidate(result.branchName); | ||
| if (!branchName) return null; | ||
| const title = result.title.trim(); | ||
| if (!title) return null; | ||
|
|
||
| return { title, branchName }; |
There was a problem hiding this comment.
sanitizeBranchCandidate is unreachable for over-length model responses
generateObjectFromMessage runs schema.safeParse(object) and returns null when it fails. Because workspaceNamesSchema enforces .max(BRANCH_NAME_MAX) (25 chars), any model response where branchName is 26+ characters causes safeParse to fail and result is null — so sanitizeBranchCandidate (which has .slice(0, BRANCH_NAME_MAX)) never executes for those cases. The entire AI rename silently no-ops.
The doc comment acknowledges this for title, but the branch name has a much tighter ceiling (25 vs 40 chars), making silent misses more likely in practice. The simplest fix is to apply sanitizeBranchCandidate before calling generateObjectFromMessage, or use a Zod .transform() on the branchName field so sanitization happens inside the schema validation step.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts
Line: 86-93
Comment:
**`sanitizeBranchCandidate` is unreachable for over-length model responses**
`generateObjectFromMessage` runs `schema.safeParse(object)` and returns `null` when it fails. Because `workspaceNamesSchema` enforces `.max(BRANCH_NAME_MAX)` (25 chars), any model response where `branchName` is 26+ characters causes `safeParse` to fail and `result` is `null` — so `sanitizeBranchCandidate` (which has `.slice(0, BRANCH_NAME_MAX)`) never executes for those cases. The entire AI rename silently no-ops.
The doc comment acknowledges this for `title`, but the branch name has a much tighter ceiling (25 vs 40 chars), making silent misses more likely in practice. The simplest fix is to apply `sanitizeBranchCandidate` before calling `generateObjectFromMessage`, or use a Zod `.transform()` on the `branchName` field so sanitization happens inside the schema validation step.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
packages/chat/src/server/desktop/title-generation/structured-generation.ts (1)
45-51: Nit: consider narrowing the fallback if Agent is missing.The string-variable dynamic import (
const agentModuleId = "@mastra/core/agent") defeats static resolution on purpose, but thethrow new Error(...)here converts a missing optional dep into a generic 500 at the caller. SincegenerateWorkspaceNamesFromPromptalready treatsnullas "skip rename", returningnullhere instead of throwing would keep the rename best-effort end-to-end and avoid noisy.catchlogs in the happy-path-with-no-model case.♻️ Proposed tweak
- if (!Agent) { - throw new Error("Mastra Agent constructor is unavailable"); - } + if (!Agent) { + return null; + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chat/src/server/desktop/title-generation/structured-generation.ts` around lines 45 - 51, The current dynamic import block throws a generic Error if Agent is missing which turns an optional dependency into a hard failure; instead, in the context of generateWorkspaceNamesFromPrompt keep the operation best-effort by returning null when Agent is not available. Locate the dynamic import using agentModuleId and the Agent symbol (Agent?: StructuredAgentCtor) in structured-generation.ts and replace the throw new Error("Mastra Agent constructor is unavailable") with a return null (or otherwise ensure the surrounding generateWorkspaceNamesFromPrompt returns null) so the caller treats this as a skip-rename case rather than a 500.packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts (1)
17-24:branchNamemax(25) may reject valid model outputs frequently.The prompt asks for "2-4 words" with only
[a-z0-9-], but 2-4 meaningful English words commonly exceed 25 chars once dash-joined (e.g.implement-user-auth-flow= 24 is borderline;add-workspace-rename-feature= 28 fails). When zod rejects,generateObjectFromMessagereturnsnull→ rename is silently skipped and the user keeps the random friendly fallback.Since
sanitizeBranchCandidatealready truncates toBRANCH_NAME_MAXdownstream, consider letting the schema be more lenient (e.g..max(60)) and letting the sanitizer enforce the hard cap. That way an over-length but otherwise correct suggestion still produces a usable branch name instead of being dropped entirely.♻️ Proposed tweak
branchName: z .string() .trim() .min(1) - .max(BRANCH_NAME_MAX) + .max(BRANCH_NAME_MAX * 2) .describe( `Git branch name in kebab-case (lowercase, dashes). 2-4 words, up to ${BRANCH_NAME_MAX} characters. Only [a-z0-9-]. No leading/trailing dashes. No prefixes.`, ),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts` around lines 17 - 24, The branchName zod rule is too strict (max set to BRANCH_NAME_MAX/~25) causing valid AI outputs to be rejected and generateObjectFromMessage to return null; relax the schema by increasing the max (e.g. `.max(60)`) on branchName instead of the current BRANCH_NAME_MAX so longer but valid kebab-case suggestions pass validation, then rely on sanitizeBranchCandidate to truncate to BRANCH_NAME_MAX (the real hard cap) before use; update the branchName.describe text if needed and keep references to branchName, BRANCH_NAME_MAX, sanitizeBranchCandidate, and generateObjectFromMessage when making the change.packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts (1)
1018-1049: Dedupe againstoldBranchName-excluded list is correct — nice.Filtering
oldBranchNameout offreshBranchesbeforededuplicateBranchNameavoids the footgun where the old (still-existing) branch would otherwise force an unnecessary suffix on the AI suggestion. And the outerif (aiNames.branchName && aiNames.branchName !== oldBranchName)short-circuits the degenerate case where the model re-produces the existing name.One minor note: after
git branch -m old newsucceeds, the worktree directory on disk stays at.worktrees/<oldBranchName>. Functionally fine (git tracks refs, not paths), but the fs layout becomes misleading. If.worktrees/<oldBranchName>/later ends up as the target for another workspace whose sanitized branch happens to equaloldBranchName,safeResolveWorktreePathwill collide with an existing dir andgit worktree addwill fail with a less obvious error than a branch-ref collision. Not a blocker — just worth capturing in the SUPER-478 follow-up or a TODO.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts` around lines 1018 - 1049, After successfully renaming the branch in generateWorkspaceNamesFromPrompt's code path (the block that calls ctx.git(worktreePath).raw(["branch","-m", oldBranchName, deduped])), also rename the on-disk worktree directory from the old sanitized name to the new one so the .worktrees/<branchName> layout matches the branch ref; do this by resolving the current worktreePath (used to create the worktree earlier), compute the new worktree path for deduped (use the same sanitization/safeResolveWorktreePath logic as when creating worktrees), check for collisions (fs.existsSync or equivalent) and then fs.rename/move the directory, and handle errors (log and fall back) so future safeResolveWorktreePath/git worktree add won't hit misleading collisions. Ensure you reference worktreePath, oldBranchName, deduped, ctx.git(...) and deduplicateBranchName in the change so reviewers can find the spot.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts`:
- Around line 1057-1067: The branch update lacks a concurrency guard similar to
expectedCurrentName, so add an expectedCurrentBranch check: when nextBranchName
!== oldBranchName set patch.branch = nextBranchName and also set
patch.expectedCurrentBranch = oldBranchName before calling
ctx.api.v2Workspace.updateNameFromHost.mutate(patch); if the server API doesn't
yet accept expectedCurrentBranch, either add that parameter on the server
handler or include a clear comment in workspace-creation.ts (near the
patch/branch logic) explaining why the asymmetry is intentional.
- Around line 1034-1067: The current sequence renames the git branch and updates
the local workspaces.row (variables/functions: worktreeGit via ctx.git,
workspaces, branch/deduped, nextBranchName, oldBranchName) before calling
ctx.api.v2Workspace.updateNameFromHost.mutate, which can leave local sqlite and
git diverged from cloud on mutation failure; fix by either (A) moving the cloud
mutation first: call ctx.api.v2Workspace.updateNameFromHost.mutate({ id:
cloudRow.id, branch: deduped, expectedCurrentName: oldBranchName? }) and only
update the local DB and perform git branch -m on success (or if git was already
renamed, revert it on cloud failure by running
worktreeGit.raw(["branch","-m",deduped,oldBranchName"])), or (B) keep the
current order but make the catch log actionable and unique (include cloudRow.id,
oldBranchName, deduped and the error) so divergence can be detected and retried;
apply the chosen fix around the block that calls worktreeGit.raw and
ctx.api.v2Workspace.updateNameFromHost.mutate.
---
Nitpick comments:
In `@packages/chat/src/server/desktop/title-generation/structured-generation.ts`:
- Around line 45-51: The current dynamic import block throws a generic Error if
Agent is missing which turns an optional dependency into a hard failure;
instead, in the context of generateWorkspaceNamesFromPrompt keep the operation
best-effort by returning null when Agent is not available. Locate the dynamic
import using agentModuleId and the Agent symbol (Agent?: StructuredAgentCtor) in
structured-generation.ts and replace the throw new Error("Mastra Agent
constructor is unavailable") with a return null (or otherwise ensure the
surrounding generateWorkspaceNamesFromPrompt returns null) so the caller treats
this as a skip-rename case rather than a 500.
In
`@packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts`:
- Around line 17-24: The branchName zod rule is too strict (max set to
BRANCH_NAME_MAX/~25) causing valid AI outputs to be rejected and
generateObjectFromMessage to return null; relax the schema by increasing the max
(e.g. `.max(60)`) on branchName instead of the current BRANCH_NAME_MAX so longer
but valid kebab-case suggestions pass validation, then rely on
sanitizeBranchCandidate to truncate to BRANCH_NAME_MAX (the real hard cap)
before use; update the branchName.describe text if needed and keep references to
branchName, BRANCH_NAME_MAX, sanitizeBranchCandidate, and
generateObjectFromMessage when making the change.
In
`@packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts`:
- Around line 1018-1049: After successfully renaming the branch in
generateWorkspaceNamesFromPrompt's code path (the block that calls
ctx.git(worktreePath).raw(["branch","-m", oldBranchName, deduped])), also rename
the on-disk worktree directory from the old sanitized name to the new one so the
.worktrees/<branchName> layout matches the branch ref; do this by resolving the
current worktreePath (used to create the worktree earlier), compute the new
worktree path for deduped (use the same sanitization/safeResolveWorktreePath
logic as when creating worktrees), check for collisions (fs.existsSync or
equivalent) and then fs.rename/move the directory, and handle errors (log and
fall back) so future safeResolveWorktreePath/git worktree add won't hit
misleading collisions. Ensure you reference worktreePath, oldBranchName,
deduped, ctx.git(...) and deduplicateBranchName in the change so reviewers can
find the spot.
🪄 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
Run ID: 14d27bdb-8db5-4772-a8f7-923d6dfced99
📒 Files selected for processing (7)
packages/chat/src/server/desktop/index.tspackages/chat/src/server/desktop/title-generation/index.tspackages/chat/src/server/desktop/title-generation/structured-generation.tspackages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-name.tspackages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.tspackages/host-service/src/trpc/router/workspace-creation/workspace-creation.tspackages/trpc/src/router/v2-workspace/v2-workspace.ts
💤 Files with no reviewable changes (1)
- packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-name.ts
| if (aiNames.title && aiNames.title !== oldWorkspaceName) { | ||
| patch.name = aiNames.title; | ||
| patch.expectedCurrentName = oldWorkspaceName; | ||
| } | ||
| if (nextBranchName !== oldBranchName) { | ||
| patch.branch = nextBranchName; | ||
| } | ||
| if (patch.name === undefined && patch.branch === undefined) { | ||
| return; | ||
| } | ||
| await ctx.api.v2Workspace.updateNameFromHost.mutate(patch); |
There was a problem hiding this comment.
No expectedCurrentBranch guard on the branch update.
patch.expectedCurrentName = oldWorkspaceName protects against a concurrent user rename of the display name, but there is no equivalent protection for branch. If the user manually renames the git branch (or the branch gets updated by some other flow) between the local workspaces.branch write above and this cloud mutation, the AI-suggested deduped will clobber their edit in the cloud row.
In practice, users rarely edit branch names in the seconds after creation, so this is low-probability. But the asymmetry with the name guard is worth either a comment explaining why it's intentional or a matching expectedCurrentBranch pattern on the server side.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts`
around lines 1057 - 1067, The branch update lacks a concurrency guard similar to
expectedCurrentName, so add an expectedCurrentBranch check: when nextBranchName
!== oldBranchName set patch.branch = nextBranchName and also set
patch.expectedCurrentBranch = oldBranchName before calling
ctx.api.v2Workspace.updateNameFromHost.mutate(patch); if the server API doesn't
yet accept expectedCurrentBranch, either add that parameter on the server
handler or include a clear comment in workspace-creation.ts (near the
patch/branch logic) explaining why the asymmetry is intentional.
There was a problem hiding this comment.
2 issues found across 7 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts">
<violation number="1" location="packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts:1037">
P1: Partial-commit risk: `git branch -m` and the local DB update (`ctx.db.update(workspaces).set({ branch: deduped })`) execute and commit before `updateNameFromHost.mutate(patch)` is called. If the remote mutate fails (network error, JWT expiry, workspace deleted, etc.), the local git branch and `workspaces.branch` carry the new name while `v2_workspaces.branch` in the cloud retains the old value — leaving a permanent desync.
Consider moving the local DB update to after the remote mutate succeeds, or adding a rollback (`git branch -m deduped oldBranchName` + reverting the local DB) in the outer catch block.</violation>
</file>
<file name="packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts">
<violation number="1" location="packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts:21">
P1: The `.max(BRANCH_NAME_MAX)` constraint on `branchName` in the zod schema causes the entire structured-output result to be rejected (returning `null`) when the model generates a branch name longer than 25 characters — `sanitizeBranchCandidate` with its `.slice(0, BRANCH_NAME_MAX)` never gets a chance to run. With a 25-char ceiling, LLM overshoots are very likely in practice, silently falling back to the friendly-fallback name.
Replace `.max(BRANCH_NAME_MAX)` with a `.transform()` that applies `sanitizeBranchCandidate` inside the schema so over-length responses are truncated rather than rejected.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
…ized AI names Addresses two review findings on v2 AI rename: - Partial-commit: previously `git branch -m` + host-local `workspaces.branch` were written before the cloud mutate, so a cloud failure left git + local in the new state and the cloud (and the web app) stuck on the old branch. Now we git-rename first, push name+branch to cloud, and only update host-local on cloud success; on cloud throw we git-rename back to the old name. - `.max()` on the zod schema rejected any overlong model output, silently no-op'ing the whole rename. Replaced with `.transform()` pipes that trim the title and sanitize the branch so overshoots are coerced instead of dropped.
…tle slug - Title cap bumped 40 → 150 and drops `.min(1)` — basically trust the small model with `.describe()` guidance. - Branch falls back to a slug of the title when the model's branchName sanitizes to nothing (e.g. all emoji), so branch rename stops being a failure point.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts (1)
1046-1088: Optional: reuse theworktreeGitclient for rollback.The rollback path at line 1077-1085 opens a fresh git client via
ctx.git(worktreePath)whileworktreeGit(line 1047) is scoped to theif (branchChanged)block. Hoisting it makes the rollback cheaper and removes one potential failure surface (ctx.git()rejecting).♻️ Proposed refactor
let deduped = oldBranchName; let gitRenamed = false; + let worktreeGit: GitClient | null = null; if (branchChanged) { const freshBranches = await listBranchNames( ctx, localProject.repoPath, ); deduped = deduplicateBranchName( aiNames.branchName, freshBranches.filter((b) => b !== oldBranchName), ); try { - const worktreeGit = await ctx.git(worktreePath); + worktreeGit = await ctx.git(worktreePath); await worktreeGit.raw(["branch", "-m", oldBranchName, deduped]); gitRenamed = true; } catch (err) { console.warn( "[workspaceCreation.create] git branch rename failed", err, ); } } @@ try { await ctx.api.v2Workspace.updateNameFromHost.mutate(patch); } catch (err) { - if (gitRenamed) { - await ctx - .git(worktreePath) - .then((g) => g.raw(["branch", "-m", deduped, oldBranchName])) + if (gitRenamed && worktreeGit) { + await worktreeGit + .raw(["branch", "-m", deduped, oldBranchName]) .catch((rollbackErr) => { console.warn( `[workspaceCreation.create] git branch rollback failed (workspace ${cloudRow.id}, ${deduped} → ${oldBranchName})`, rollbackErr, ); }); } throw err; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts` around lines 1046 - 1088, Hoist the worktreeGit variable so the same git client is used for both the initial rename and any rollback: declare let worktreeGit: ReturnType<typeof ctx.git> | undefined before the branchChanged try block, assign worktreeGit = await ctx.git(worktreePath) inside that try (then call worktreeGit.raw([...]) and set gitRenamed = true), and in the catch of the updateNameFromHost.mutate rollback use worktreeGit.raw(["branch", "-m", deduped, oldBranchName]) if worktreeGit is defined instead of calling ctx.git(worktreePath) again; preserve existing error handling and fallback behavior if worktreeGit is undefined.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts`:
- Around line 1046-1088: Hoist the worktreeGit variable so the same git client
is used for both the initial rename and any rollback: declare let worktreeGit:
ReturnType<typeof ctx.git> | undefined before the branchChanged try block,
assign worktreeGit = await ctx.git(worktreePath) inside that try (then call
worktreeGit.raw([...]) and set gitRenamed = true), and in the catch of the
updateNameFromHost.mutate rollback use worktreeGit.raw(["branch", "-m", deduped,
oldBranchName]) if worktreeGit is defined instead of calling
ctx.git(worktreePath) again; preserve existing error handling and fallback
behavior if worktreeGit is undefined.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3ec2a53b-7b7c-4489-aea6-b4258f7e2443
📒 Files selected for processing (2)
packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.tspackages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts
Per-field gating in the caller already skips the branch rename when branchName is empty; no need to salvage. If the model's output is bad, skip the rename.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts`:
- Around line 20-25: trimTitle currently strips trailing punctuation before
truncating, so a punctuation character can remain if it falls exactly at
WORKSPACE_TITLE_MAX; update trimTitle (reference function trimTitle and compare
to sanitizeBranchCandidate behavior) to perform the truncation first then
re-apply the trailing-punctuation strip (e.g., trim/slice then
.replace(/[\s.,;:!?-]+$/g, "") and final .trim()) so titles are idempotently
free of trailing punctuation even after truncation.
🪄 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
Run ID: 1b6d3641-63b9-455f-a655-0edb0a3e0d3e
📒 Files selected for processing (1)
packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts
| function trimTitle(raw: string): string { | ||
| return raw | ||
| .trim() | ||
| .replace(/[\s.,;:!?-]+$/g, "") | ||
| .slice(0, WORKSPACE_TITLE_MAX); | ||
| } |
There was a problem hiding this comment.
Consider re-trimming trailing punctuation after slice.
Unlike sanitizeBranchCandidate (which re-applies /-+$/g after truncation on line 17), trimTitle strips trailing punctuation before slice(0, 150). If the model emits a title longer than WORKSPACE_TITLE_MAX and character 150 happens to be whitespace, ., ,, -, etc., it will survive into v2_workspaces.name. Low likelihood at 150 chars, but trivial to make idempotent:
Proposed tweak
function trimTitle(raw: string): string {
return raw
.trim()
.replace(/[\s.,;:!?-]+$/g, "")
- .slice(0, WORKSPACE_TITLE_MAX);
+ .slice(0, WORKSPACE_TITLE_MAX)
+ .replace(/[\s.,;:!?-]+$/g, "");
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function trimTitle(raw: string): string { | |
| return raw | |
| .trim() | |
| .replace(/[\s.,;:!?-]+$/g, "") | |
| .slice(0, WORKSPACE_TITLE_MAX); | |
| } | |
| function trimTitle(raw: string): string { | |
| return raw | |
| .trim() | |
| .replace(/[\s.,;:!?-]+$/g, "") | |
| .slice(0, WORKSPACE_TITLE_MAX) | |
| .replace(/[\s.,;:!?-]+$/g, ""); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts`
around lines 20 - 25, trimTitle currently strips trailing punctuation before
truncating, so a punctuation character can remain if it falls exactly at
WORKSPACE_TITLE_MAX; update trimTitle (reference function trimTitle and compare
to sanitizeBranchCandidate behavior) to perform the truncation first then
re-apply the trailing-punctuation strip (e.g., trim/slice then
.replace(/[\s.,;:!?-]+$/g, "") and final .trim()) so titles are idempotently
free of trailing punctuation even after truncation.
There was a problem hiding this comment.
2 issues found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts">
<violation number="1" location="packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts:24">
P2: `trimTitle` truncates with a raw slice, so titles can still be cut mid-word (and may end with punctuation after truncation), which violates the new title constraints.</violation>
<violation number="2" location="packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts:24">
P3: Re-trim trailing punctuation after `slice` — if the model returns a title longer than 150 chars and the cut lands on a space, comma, period, etc., those characters will leak into the workspace name. `sanitizeBranchCandidate` already handles this correctly by re-applying its cleanup regex after truncation; `trimTitle` should do the same.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| return raw | ||
| .trim() | ||
| .replace(/[\s.,;:!?-]+$/g, "") | ||
| .slice(0, WORKSPACE_TITLE_MAX); |
There was a problem hiding this comment.
P2: trimTitle truncates with a raw slice, so titles can still be cut mid-word (and may end with punctuation after truncation), which violates the new title constraints.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts, line 24:
<comment>`trimTitle` truncates with a raw slice, so titles can still be cut mid-word (and may end with punctuation after truncation), which violates the new title constraints.</comment>
<file context>
@@ -2,23 +2,43 @@ import { generateObjectFromMessage } from "@superset/chat/server/desktop";
+ return raw
+ .trim()
+ .replace(/[\s.,;:!?-]+$/g, "")
+ .slice(0, WORKSPACE_TITLE_MAX);
+}
+
</file context>
| return raw | ||
| .trim() | ||
| .replace(/[\s.,;:!?-]+$/g, "") | ||
| .slice(0, WORKSPACE_TITLE_MAX); |
There was a problem hiding this comment.
P3: Re-trim trailing punctuation after slice — if the model returns a title longer than 150 chars and the cut lands on a space, comma, period, etc., those characters will leak into the workspace name. sanitizeBranchCandidate already handles this correctly by re-applying its cleanup regex after truncation; trimTitle should do the same.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts, line 24:
<comment>Re-trim trailing punctuation after `slice` — if the model returns a title longer than 150 chars and the cut lands on a space, comma, period, etc., those characters will leak into the workspace name. `sanitizeBranchCandidate` already handles this correctly by re-applying its cleanup regex after truncation; `trimTitle` should do the same.</comment>
<file context>
@@ -2,23 +2,43 @@ import { generateObjectFromMessage } from "@superset/chat/server/desktop";
+ return raw
+ .trim()
+ .replace(/[\s.,;:!?-]+$/g, "")
+ .slice(0, WORKSPACE_TITLE_MAX);
+}
+
</file context>
| .slice(0, WORKSPACE_TITLE_MAX); | |
| .slice(0, WORKSPACE_TITLE_MAX) | |
| .replace(/[\s.,;:!?-]+$/g, ""); |
…erate directly
The wrapper in packages/chat was pure indirection — chat already has @mastra/core
as a direct dep. Removed it, added @mastra/core to host-service, and call
agent.generate({ structuredOutput }) directly from ai-workspace-names. Also tightened
getSmallModel's return type to MastraModelConfig | null so callers don't need casts.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts (1)
20-25:⚠️ Potential issue | 🟡 MinorPost-slice trailing punctuation can still survive.
Same asymmetry as flagged previously:
sanitizeBranchCandidatere-applies/-+$/gafterslice(L17), buttrimTitlestrips punctuation before truncation only, so if char 150 lands on whitespace/.,;:!?-, it will persist intov2_workspaces.name. Low-probability at 150 chars, but trivial to make idempotent.Proposed tweak
function trimTitle(raw: string): string { return raw .trim() .replace(/[\s.,;:!?-]+$/g, "") - .slice(0, WORKSPACE_TITLE_MAX); + .slice(0, WORKSPACE_TITLE_MAX) + .replace(/[\s.,;:!?-]+$/g, ""); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts` around lines 20 - 25, trimTitle currently strips trailing punctuation before truncating which can leave post-slice trailing punctuation in the result; update trimTitle (the function named trimTitle that uses WORKSPACE_TITLE_MAX) to perform the truncation first (slice to WORKSPACE_TITLE_MAX) and then re-apply the trailing-punctuation removal (same regex style used in sanitizeBranchCandidate, e.g. /[\s.,;:!?-]+$/g) so the returned title is idempotently free of trailing punctuation even when truncation cuts mid-run of punctuation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In
`@packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts`:
- Around line 20-25: trimTitle currently strips trailing punctuation before
truncating which can leave post-slice trailing punctuation in the result; update
trimTitle (the function named trimTitle that uses WORKSPACE_TITLE_MAX) to
perform the truncation first (slice to WORKSPACE_TITLE_MAX) and then re-apply
the trailing-punctuation removal (same regex style used in
sanitizeBranchCandidate, e.g. /[\s.,;:!?-]+$/g) so the returned title is
idempotently free of trailing punctuation even when truncation cuts mid-run of
punctuation.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4ddd592d-3999-4ee8-bc37-9bd5508c18ff
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (3)
packages/chat/src/server/shared/small-model/get-small-model.tspackages/host-service/package.jsonpackages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts
✅ Files skipped from review due to trivial changes (1)
- packages/host-service/package.json
Pulls the ~80-line post-create block out of workspaceCreation.create into applyAiWorkspaceRename in ai-workspace-names.ts — same file as the generator so naming + applying live together. listBranchNames moves to its own util so both callers can share it. Call site in the create handler is now a three-line fire-and-forget.
Sherif の multiple-dependency-versions エラーを解消。upstream superset-sh#3692 (aceb0fd) が host-service の @mastra/core を 1.26.0-alpha.3 に アップデートしたが、apps/desktop と packages/chat は 1.25.0 のまま だった。upstream/main では 3 箇所全て 1.26.0-alpha.3 で統一されているため、 fork でも揃える。
Summary
structuredOutput+ zod schema) returning{ title, branchName }instead of two separate text-gen calls with a naiveslice(0, 20)that was producing mid-word truncations like"New v2 workspaces na".v2_workspaces.namevia the existingupdateNameFromHostmutation (now extended to accept optionalbranchalongside optionalname).git branch -minside the worktree, thenhost-service/workspaces.branch+v2_workspaces.branchboth updated. Dedup still runs against fresh branches in case of collision.getSmallModel()so the user's Claude subscription is hit the same way asgenerateTitleFromMessage.jsonPromptInjection: truekeeps it working over OAuth.Test plan
friendlyFallbackto a kebab-case slug.deduplicateBranchNameappends a suffix.git branch -mfailure path logs a warning and the title still applies.{ title, branchName }.Summary by cubic
Generates both the v2 workspace title and git branch from the create prompt in one structured-output call and applies them together. Refactors the post-create rename into
applyAiWorkspaceRenamewith a sharedlistBranchNamesutil for simpler, more reliable updates.New Features
{ title, branchName }(zod-transformed). Title ≤150 chars with whole words; branch is kebab-case ≤25 chars, sanitized and deduped.v2Workspace.updateNameFromHost; host-local branch only after cloud success.v2Workspace.updateNameFromHostnow accepts optionalnameand/orbranchwithexpectedCurrentName(requires at least one).Agent.generatewith structured output; adds@mastra/core.getSmallModel()returnsMastraModelConfig | null.Bug Fixes
Written for commit 920d7c2. Summary will update on new commits.
Summary by CodeRabbit
New Features
Improvements