Skip to content

fix(server): restore worktree branch naming in the v2 orchestrator - #5309

Merged
juliusmarminge merged 1 commit into
t3code/codex-turn-mappingfrom
t3code/v2-thread-title-branch-naming
Aug 4, 2026
Merged

fix(server): restore worktree branch naming in the v2 orchestrator#5309
juliusmarminge merged 1 commit into
t3code/codex-turn-mappingfrom
t3code/v2-thread-title-branch-naming

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Aug 4, 2026

Copy link
Copy Markdown
Member

Stacked on #2829. Complements #5176, which restored title generation — this PR covers the other half of the regression: worktree branch naming.

Problem

On the v2 orchestrator, threads stay on their temporary t3code/<hash> branch forever. ThreadLaunchService only generated a branch name when workspaceStrategy.branch === undefined, but the web client always passed a temporary t3code/<hash> name — so generation never fired, and unlike v1 there was no later rename pass.

Fix

The server now owns worktree naming:

  • Without an explicit branch, the worktree is provisioned immediately under a server-invented temporary t3code/<8hex> name (randomUuidV4 + shared buildTemporaryWorktreeBranchName).
  • After the workspace metadata dispatch, a background fork generates the real name, renames via git branch -m, and dispatches a :branch-rename metadata update. Name generation never sits on the critical path in front of worktree creation, the setup script, or the provider turn (matching v1's concurrency, where the rename raced the turn). This also removes the previous awaited-generation path, so branchless launches (scheduled tasks, MCP-driven) no longer block provisioning on an LLM call.
  • If generation or the rename fails, the temporary name sticks with a logged warning.
  • The web client stops inventing temporary branch names. branch stays optional in the contract: explicit user-chosen branch names are respected verbatim, and temporary names from clients that still send them (mobile's offline outbox) are renamed server-side the same way.

Notes

  • Generated branch names follow the existing v2 feature/<slug> convention rather than v1's t3code/<slug> wrapper; happy to align to v1 if the namespace matters downstream.
  • Minor UX: the sidebar shows no branch for a beat longer on brand-new threads (until the workspace metadata event lands) since the client no longer has a name to show optimistically; the "Preparing worktree" run phase covers that window.

Testing

  • 5 new ThreadLaunchService tests: server-invented temp names, off-critical-path rename (run reaches starting while generation is gated behind a deferred), explicit-branch passthrough, generation-failure fallback, existing-worktree rename.
  • Full orchestration-v2 suite: 558 passed, 54 files (on top of the merged fix(orchestration-v2): restore generated thread titles #5176).
  • Typecheck clean for t3 and @t3tools/web.

🤖 Generated with Claude Code


Note

Medium Risk
Changes thread launch provisioning and git branch lifecycle on the critical path for new worktrees; failures degrade to temp branch names rather than blocking launch.

Overview
Restores v2 thread launch behavior where worktrees stayed on client-supplied t3code/<hash> branches and never got LLM-generated names, because ThreadLaunchService used that name verbatim and did not run a rename pass like v1.

Branch naming — When no explicit branch is sent, the server provisions immediately under a temporary t3code/<8hex> name (via buildTemporaryWorktreeBranchName / randomUuidV4), instead of awaiting generateBranchName or using the removed thread-<id> fallback. After workspace metadata is written, a background fork generates a real branch name, git.renameBranch, and a thread.metadata.update with command id branch-rename; failures are logged and the temp name remains. Explicit user branches are unchanged; client-supplied temp names (legacy/mobile) still get renamed the same way, including existing_worktree launches.

Web clientChatView no longer passes branch in prepareWorktree (removed buildTemporaryWorktreeBranchName / randomHex); the server owns the initial ref.

TestsThreadLaunchService.test harness mocks renameBranch and adds cases for server naming, off-critical-path rename, explicit branch, generation failure fallback, and existing-worktree rename.

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

Note

Restore worktree branch naming to the v2 orchestrator from the client

  • Moves temporary worktree branch name generation (t3code/<hash>) from the client (ChatView.tsx) into ThreadLaunchService.ts using buildTemporaryWorktreeBranchName and randomUuidV4.
  • After provisioning, the server asynchronously generates a proper branch name and calls git.renameBranch, then updates thread metadata; if generation or rename fails, the temporary branch is kept without affecting provisioning.
  • Explicit branches passed by the client are preserved as-is; branch generation only runs for server-assigned temporary branches.
  • Adds tests covering no-branch, deferred rename, explicit branch, failure fallback, and existing-worktree rename scenarios in ThreadLaunchService.test.ts.
  • Behavioral Change: clients no longer send a branch when preparing a worktree; the thread-<suffix> fallback branch name from the old server path is removed.

Macroscope summarized 7190bf2.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 39a3aeda-de24-45a6-84e2-956616c48785

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 4, 2026
) {
const oldBranch = branch;
const worktreeCwd = worktreePath;
yield* generateBranchNameFor(worktreeCwd, initialMessage).pipe(

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.

🟠 High orchestration-v2/ThreadLaunchService.ts:336

The background branch-rename chain can leave the thread projection permanently out of sync with the actual Git branch. git.renameBranch runs before threads.dispatch, and both are wrapped in a single Effect.catchCause that only logs. If the rename succeeds but the metadata dispatch fails (e.g. a transient store error), the worktree is on the generated branch while the thread projection keeps the temporary t3code/<hash> branch. Later Git actions use the stale branch name. Consider retrying the metadata update, rolling back the rename on dispatch failure, or otherwise reconciling the two before swallowing the error.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/ThreadLaunchService.ts around line 336:

The background branch-rename chain can leave the thread projection permanently out of sync with the actual Git branch. `git.renameBranch` runs before `threads.dispatch`, and both are wrapped in a single `Effect.catchCause` that only logs. If the rename succeeds but the metadata dispatch fails (e.g. a transient store error), the worktree is on the generated branch while the thread projection keeps the temporary `t3code/<hash>` branch. Later Git actions use the stale branch name. Consider retrying the metadata update, rolling back the rename on dispatch failure, or otherwise reconciling the two before swallowing the error.

Comment thread apps/server/src/orchestration-v2/ThreadLaunchService.ts Outdated
}),
),
Effect.forkIn(preparationScope),
);

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.

Branch rename skips mid-flight guard

Medium Severity

Title generation re-checks canReplaceThreadTitle on the current projection before applying, so a user rename mid-generation wins. The new branch rename path captures oldBranch up front, then always runs git.renameBranch and a metadata update with no re-check that the thread branch is still temporary. A branch change during the background LLM call can be overwritten, or a git rename can run against a stale name.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit dddc16b. Configure here.

@macroscopeapp

macroscopeapp Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

2 blocking correctness issues found. Multiple unresolved review comments identify race conditions and data consistency issues in the new background branch rename and title generation logic. These substantive bugs (thread projection desync, non-atomic read-then-write patterns) warrant human review before merging.

You can customize Macroscope's approvability policy. Learn more.

@Yusuf007R

Copy link
Copy Markdown

Hey! I’ve been working on the same title issue in #5176. It handles generation through the durable V2 outbox, avoids overwriting manual renames, and covers web, mobile, and already-created threads. Could we reuse that part here alongside the branch-naming changes?

The v2 launch path only generated a branch name when no branch was
passed, but the web client always passed a temporary t3code/<hash>
branch — so the guard never fired and threads kept the hash as their
branch, with no later rename pass (unlike v1).

The server now owns worktree naming: without an explicit branch, the
worktree is provisioned immediately under a server-invented temporary
t3code/<hash> name, then renamed to a generated name in a background
fork so name generation never delays provisioning or the provider turn.
Temporary names from clients that still send them (mobile outbox) are
renamed the same way, and stick if generation fails. The web client
stops inventing temporary branch names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge force-pushed the t3code/v2-thread-title-branch-naming branch from dddc16b to 7190bf2 Compare August 4, 2026 11:19
@juliusmarminge juliusmarminge changed the title fix(server): restore thread title and worktree branch generation in the v2 orchestrator fix(server): restore worktree branch naming in the v2 orchestrator Aug 4, 2026
@github-actions github-actions Bot added size:M 30-99 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Aug 4, 2026
@juliusmarminge
juliusmarminge merged commit afcb4ab into t3code/codex-turn-mapping Aug 4, 2026
16 of 17 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/v2-thread-title-branch-naming branch August 4, 2026 11:24

@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 high effort and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix is ON, but it could not run because the branch was deleted or merged before autofix could start.

Reviewed by Cursor Bugbot for commit 7190bf2. Configure here.

let branch: string | null;
if (input.workspaceStrategy.type === "worktree" && requestedBranch === undefined) {
const uuid = yield* randomUuidV4;
branch = buildTemporaryWorktreeBranchName(() => uuid.replaceAll("-", ""));

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.

Retry orphans prior worktree

Medium Severity

Branchless worktree launches now invent a fresh random t3code/&lt;hash&gt; on every prepareInBackground run. Launch re-schedules while a run is still preparing, and reservePreparation is only in-memory, so a crash or lost-response retry can create a second worktree and orphan the first. Web previously sent a stable client temp name that made that retry collide instead of leak.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7190bf2. Configure here.

juliusmarminge added a commit that referenced this pull request Aug 4, 2026
…5309)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants