[Bug]: Thread in the project folder keeps its starting branch and MR after the agent runs `git checkout`#33278

opened6d agobyRreech-jeanphilippe

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. Open a project whose folder is a git checkout on feature/A, where feature/A has an open MR/PR.
  2. Start a thread from the composer in the project folder (no worktree). The thread is saved with branch: feature/A.
  3. Ask the agent to review another MR. It runs git checkout feature/B in the project folder during the first turn.
  4. Let the turn finish, then look at the thread's row in the sidebar.

Expected behavior

The row shows feature/B and feature/B's MR, since the thread is now working there.

Actual behavior

The row keeps showing feature/A and feature/A's MR. git branch --show-current in the project folder returns feature/B. The reflog shows the agent's checkout about 30 seconds after the thread started.

Later messages only fix it sometimes. In my case, 4 threads in 4 clones of the same repo all ran this flow within 3 minutes:

ThreadBranch saved at startAgent checkoutLater turnsRow today
115:15:00 feature/A115:15:37 → feature/B1turn 2 at 16:07 corrected itcorrect
215:16:07 feature/A215:17:14 → feature/B2nonestale
315:17:12 feature/A315:17:47 → feature/B3turn 2 at 15:30 did not correct itstale
415:18:09 feature/A415:18:35 → feature/B4turn 2 at 15:32 did not; turn 3 at 16:48 didcorrect

Impact

Major degradation or frequent failure

Version or commit

T3 Code (Beta) 0.1.7 desktop. Code paths unchanged on main @ c73b38b3.

Environment

Linux 6.14 (Ubuntu), Claude agent, GitLab-hosted repo (MRs)

Cause (from reading main @ c73b38b3)

  1. The branch is saved once, when the thread starts (ThreadLaunchService.ts:470-478).
  2. WorktreeBranchSync sees the checkout but only updates threads that have a worktreePath. getThreadWorktreePaths filters with json_extract(payload_json, '$.worktreePath') IS NOT NULL (ProjectionStore.ts:6768), so threads in the project folder never match.
  3. The turn-end refresh reads the live branch and then stops when it differs from the saved one: if (!thread || thread.branch !== local.refName) return; (RunFinalizationService.ts:105-111). It also stops early when the new branch is the default branch (local.isDefaultRef).
  4. The only remaining fix-up runs in the client when a message is sent (useThreadLifecycle.tsx:534-538 via resolveLocalCheckoutBranchMismatch). It compares against whatever git status the browser has cached, which explains the inconsistent results above. The client's live sync skips threads that have already started (GitActionsControl.tsx:1082).

The MR badge is looked up from the saved branch, so it goes stale along with it.

Suggested fix

In RunFinalizationService.refresh, when the live branch differs from thread.branch and the run is still the thread's current run, dispatch thread.metadata.update with branch: local.refName, expectedBranch: thread.branch and expectedWorktreePath: thread.worktreePath. Do this for the thread whose turn just ended, not for every thread sharing the folder. That way older threads in the same project keep the branch they actually worked on, which is presumably why WorktreeBranchSync leaves project-folder threads alone. Then let the existing MR lookup run on the new branch.

Related: #11078 and its fix PR #15989 (agent moves to another worktree; I couldn't tell whether "in-place branch reconciliation" there covers threads with no worktree), #28463 (worktree branch rename), #31177 (agent-launched project-folder threads saved with no branch), #7409.