fix(server): threads launched without a branch record the checkout's#34051

open3
main
GitHub fork: pr/launch-branch
opened4d agobyRradomirml
View on GitHubTwo-way sync enabledPR last imported 3d ago

Problem

A thread launched in the project root or an existing worktree records no branch when the caller names none. The thread's folder can be on a branch, but the thread keeps branch: null. MCP launches (t3_thread_launch, or t3 thread start) never name one. The composer usually sends the checked-out branch, but not when the thread-details menu hides Git actions and Changes, because the control that saves it is then not mounted (found by @brandon-julio-t below).

As a result, such a thread shows no branch in the sidebar. It also misses the pull request refresh after each turn, which needs the branch.

Reproduce:

  1. Check out a feature branch in a project folder.
  2. Run t3 thread start <project> "go" without --worktree.
  3. Run t3 thread show <id> --json. thread.branch is null.

Change

When the launch names no branch, ThreadLaunchService reads the branch checked out in the thread's folder and records it, as the composer does. It uses the worktree path if there is one, otherwise the project root. A folder that is not a Git checkout, or has a detached HEAD, still records no branch, because currentBranch returns null there and errors fall back to null. Reused worktrees are not touched, as before.

Scope and approval

This is a small fix for an obvious bug: the two launch paths record different metadata for the same folder. The change is one server function plus a test. It does not change any contract or client.

Verification

  • I added cases to apps/server/src/orchestration-v2/ThreadLaunchService.test.ts for a launch in an existing worktree and in the project root, plus a failed branch lookup that still lets the launch prepare and records no branch. Without the fix, the worktree and root cases fail with expected null to equal 'task-branch' and expected null to equal 'main'. Without the error fallback, the failed-lookup case fails. With the fix, all 49 tests in the file pass.
  • vp run check:changed passes: format, lint, knip, typecheck, and the tests next to the change.
  • Runtime check with vp run dev and the Replay (dev) provider, against a scratch repo checked out on feature/demo.

Before the fix:

1$ git -C demo-repo branch --show-current
2feature/demo
3$ t3 thread start --provider devReplay --model showcase demo-repo go
4mcp:7d09bf6c-968e-4133-b3d2-9c7b7341f406
5$ t3 thread show mcp:7d09bf6c-968e-4133-b3d2-9c7b7341f406 --json | jq '.thread | {branch, worktreePath}'
6{"branch":null,"worktreePath":null}

After the fix:

1$ git -C demo-repo branch --show-current
2feature/demo
3$ t3 thread start --provider devReplay --model showcase demo-repo go
4mcp:7cec2b91-6062-487f-a613-96a060ecda0b
5$ t3 thread show mcp:7cec2b91-6062-487f-a613-96a060ecda0b --json | jq '.thread | {branch, worktreePath}'
6{"branch":"feature/demo","worktreePath":null}

I did not check the sidebar in a client. The sidebar already renders thread.branch, and this change only fills that field.

Done with Claude Opus 5.5 in Claude Code (T3 Code).

Checks

21 successful · 10 skipped
All checks passed
df1686b

Added 1 commit

  • fix(server): threads launched without a branch record the checkout's18d260a
Synced from GitHub

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — Launches without an explicit branch now record the checkout’s current branch instead of null, while preserving reused worktrees and explicit branches. This changes default thread metadata behavior and requires human confirmation.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

When a launch omits a branch, ThreadLaunchService now records the current branch from the worktree or project root. If the lookup fails, it records null. Tests cover both workspace types.

Changes: Launch branch metadata

Layer / File(s)Summary
Resolve branch during launch
apps/server/src/orchestration-v2/ThreadLaunchService.ts, apps/server/src/orchestration-v2/ThreadLaunchService.test.ts
For non-reused workspaces, launch preparation records the requested branch or looks up the current branch from the workspace path. A failed lookup records null. Tests check existing-worktree and root workspaces.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Possibly related issues

  • pingdotgg/t3code#31177: The issue reports missing branch metadata when a project-folder launch omits a branch. This change records the current branch from the project root.

Suggested reviewers: juliusmarminge

Merge Risk: External image Minimal · up to df168

Threads launched without a branch now record the checkout's branch, and a failed lookup records no branch. No actionable merge risk remains.

Pre-merge checks: Passed 4

Check nameStatusExplanation
Title checkPassedThe title clearly identifies the main server fix for recording the checkout branch when a thread launch does not specify one. Although the possessive ending is grammatically incomplete, the title rema…
Description checkPassedThe description includes all required sections: Problem, Change, Scope and approval, and Verification. It explains the reproduction, implementation, scope rationale, focused tests, broader checks, run…
Linked Issues checkPassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes checkPassedCheck skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title clearly identifies the main server fix for recording the checkout branch when a thread launch does not specify one. Although the possessive ending is grammatically incomplete, the title remains specific and directly related to the changes.

Full details: Description check

Explanation

The description includes all required sections: Problem, Change, Scope and approval, and Verification. It explains the reproduction, implementation, scope rationale, focused tests, broader checks, runtime validation, and remaining unverified client coverage.

Finishing Touches

Generate unit tests (beta)

  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
External image Recent review info

External image Run configuration

External image Commits

Reviewing files that changed from the base of the PR and between 7723365d10ed3da15bfdfcbbc6c228f23c233fa3 and df1686b8ade66f444521b257bf416782c0ba6b00.

External image Files selected for processing: 1

  • apps/server/src/orchestration-v2/ThreadLaunchService.test.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.

Note

Written by gpt-6.1-sol on behalf of Maria

Verified in a real dev environment

verified exact head 18d260a596bb2e35c43052c2e3217fa502e769a0 on this linux boat vm. no code changes were needed.

reproduced on github/main (79bca436b2) in a separate worktree, then stopped main before starting the pr. used isolated dev state, a real git repository, real codex gpt-6.1-sol turns through the local proxy, and repo playwright with /usr/bin/google-chrome. projects and threads were created through real cli/mcp server paths without mocks or fixtures.

  • main: the provider returned feature/launch-proof, but the thread stored branch: null and the sidebar omitted the branch.
  • pr: the same root launch stored feature/launch-proof, and the web sidebar displayed it.
  • existing worktree: mcp launch stored feature/worktree-proof; the real provider ran there and returned that branch.
  • explicit branch: a new worktree retained feature/explicit-proof, matching the provider checkout.
  • detached head: an existing worktree retained branch: null; the provider confirmed detached head.
  • web client: approved real tool requests, switched threads, and sent a follow-up through the composer. it completed with branch metadata intact.

short runtime transcript from t3 thread show --json:

text
1main root: branch=null, provider reply=feature/launch-proof
2pr root: branch=feature/launch-proof, status=completed
3pr existing worktree: branch=feature/worktree-proof, status=completed
4pr explicit branch: branch=feature/explicit-proof, status=completed
5pr detached head: branch=null, status=completed
6pr root after web follow-up: branch=feature/launch-proof, runCount=2, status=completed

full command results

all 48 existing launch-service tests passed on blacksmith with vp test run apps/server/src/orchestration-v2/ThreadLaunchService.test.ts. inspected ci reported 32 successful checks, zero failing or pending.

before on main: completed real codex turn with no sidebar branch

after on pr: same repository and prompt with branch displayed in sidebar

real web approval, composer follow-up, and existing-worktree branch

inspected the captures and verified uploaded video playback and seeking in chrome.

Not verified

native ios/android, the electron shell, macos/windows, other providers, and remote/relay/tunnel connections were not exercised on this linux web run. automatic remote pull-request refresh was not exercised because the verification repository has no remote pr. non-git folders and git lookup failures were not exercised through a provider turn.

Added 1 commit

  • Merge remote-tracking branch 'origin/main' into pr/launch-branch7723365

Evidence

Create a Git project on feature/launch-proof, start a thread through the CLI without branch or worktree options, wait for its replay turn, and open the thread. Real Chromium web client, 1280×800, light theme, fresh synthetic data on each commit.

The project-root scenario fails on base and passes on head. The sidebar omits the branch before the fix and displays it afterward. Supplemental CLI reads confirm thread.branch changes from null to feature/launch-proof.

Passing proof scenario · Sanitized branch observations · Supplemental comparison driver · MCP launch helper

Limitation: the completed-turn existing-worktree repetition recorded feature/existing-proof on both commits, so that path did not reproduce the lasting failure.

AI-assisted; verified with the tests and tools listed above.

Not shown:

  • The existing_worktree path did not reproduce a lasting branch omission: after its initial replay turn, persisted metadata contained feature/existing-proof on both commits. Immediate idle-launch reads sometimes returned null, but were sensitive to asynchronous workspace preparation.
  • Detached HEAD and non-Git folder behavior were not exercised.
Bbinjamshidreviewed4d ago
Synced from GitHub

Recording the checked-out branch when the launch names none matches what the composer sends, and the fallback is safe: GitWorkflowService.currentBranch already returns null for non-repos and detached HEADs, and orElseSucceed covers the 5s timeout or other git errors without failing the launch. Reused worktrees and explicitly named branches are unchanged.

  1. low, apps/server/src/orchestration-v2/ThreadLaunchService.test.ts:1079: the mock returns "main" for any cwd other than /wt/task, so the project-root case would still pass if the code looked up the wrong folder (for example a stale worktreePath). Matching cwd === "/repo" (the harness's workspaceRoot) explicitly, and adding one case where currentBranch fails and the launch still records null, would pin down both the path choice and the error fallback.
Synced from GitHub

There is also a composer/UI route to the missing-branch case this PR addresses. The statement that threads made in the app cannot have this problem is too broad.

In a macOS Nightly recording supplied by a user, a Git project is checked out on master. With Git actions and Changes hidden in the thread-details menu, new current-checkout threads have no sidebar branch. Enabling Branch and pull request makes the details card show master, but the sidebar still has no branch. After Reset to default, another new thread shows master in the sidebar. Existing older threads in that project also show master.

Source inspection on main at f6433dba45 explains the dependency:

  • ThreadDetailsPanel.tsx mounts GitActionsControl only when Git actions or Changes is visible.
  • GitActionsControl.tsx owns the effect that calls resolveLiveThreadBranchUpdate and persists the live branch into the draft. Hiding both rows removes that effect.
  • BranchToolbarBranchSelector.tsx can display the current branch without saving it for a local/current-checkout draft; its automatic defaulting effect is restricted to worktree mode.
  • SidebarThreadRow.tsx renders the saved thread.branch; when it is null and there is no other metadata, the row collapses and moves the machine/provider icons beside the title, truncating more of it.

Suggested additional regression/verification case: start a current-checkout thread in a synthetic Git repo with Git actions and Changes both hidden, including with Branch and pull request visible. The resulting server thread should record the checkout's branch independently of those presentation settings. Repeat with Reset to default as the control.

The server fallback in this PR appears to cover that launch without depending on mounted UI controls. I inspected the patch, but have not run this UI scenario against the PR head, so that remains a verification request rather than a claimed passing result. I have not attached the user's recording because it contains private workspace information.

Investigation and comment by GPT-6.1 Sol in the Codex harness through T3 Code.

Added 1 commit

  • test(server): pin the folder and the failure fallback for launch branchesdf1686b
Synced from GitHub

Thanks for the review and the extra case.

@binjamshid: df1686b8ad takes both suggestions. Each row now names the folder it expects (/repo for the root, /wt/task for the worktree), so a lookup in the wrong folder fails the worktree case. A new row makes currentBranch fail in an existing worktree, then checks that the run still reaches starting and records no branch. Without orElseSucceed, that row fails, because the launch's preparation fails.

@brandon-julio-t: the composer starts threads through the launchThread RPC, which runs the same ThreadLaunchService.launch (ws.ts → ThreadMessageIntake.launchThread). When the composer sends no branch because Git actions and Changes are hidden, this change records the checked-out one. I have not run that UI scenario either. I updated the description, which said app threads could not miss the branch.

Done with Claude Opus 5.5 in Claude Code (T3 Code).

Bbinjamshidreviewed3d ago
Synced from GitHub

The test point is fixed at df1686b8ad. The mock now answers only for /repo and /wt/task (ThreadLaunchService.test.ts:1083), so a lookup in the wrong folder gets null and fails its row, and the new failing-lookup row checks the run still reaches starting with no branch recorded (:1077, :1110-1111).