Skip to content

[Bug]: Worktree threads lose their PR badge once the agent switches branches inside the worktree #4831

Description

@LouisHaftmann

Before submitting

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

Area

apps/web

Steps to reproduce

  1. Start a thread with a dedicated worktree.
  2. Let the agent create/switch to its own branch inside that worktree (it usually renames off the generated t3code/<slug> branch).
  3. Open a PR for that branch.
  4. Look at the thread row in the sidebar.

Expected behavior

The PR number/badge shows on the thread row.

Actual behavior

No badge. The server finds the PR fine — the client discards it.

#4460 removed the dedicated-worktree bypass in resolveThreadPr, so every row now requires gitStatus.refName === thread.branch. But nothing ever writes the worktree's new HEAD back to projection_threads.branch (no branch-update event exists anywhere in apps/server/src), so the stored branch goes stale the moment the agent switches branches — and the badge disappears permanently.

Roughly half of my active worktree threads are in this state. Concrete shape: thread branch stays t3code/<slug>, worktree HEAD is fix/<something-the-agent-named>, and that branch has an open PR. gitStatus.pr contains it; resolveThreadPr returns null.

Not covered by #4752 / #4755 — that fix retains only merged/closed PRs and only for local checkouts ("worktree threads clear their snapshot on branch mismatch"). It restores zero badges in this case.

Impact

Minor bug or occasional failure

Version or commit

0.0.31-nightly.20260729.942 (regression from #4460, 7248877)

Environment

Linux, server started via npx t3@nightly server, web UI

Notes

A plain revert of #4460 would reintroduce the stacked-PR mislabel it fixed. The fix that satisfies both is to keep the stored branch in sync with the worktree's live HEAD, so the equality check passes legitimately and the label and PR always describe the same branch. That does mean a worktree parked on a stacked head branch would show that branch's PR — arguably correct, but a maintainer call.

Agent prompt to fix
In pingdotgg/t3code: a thread's stored branch (`projection_threads.branch`) goes stale
when an agent switches branches inside the thread's dedicated worktree. Since #4460
(commit 724887717), `resolveThreadPr` in apps/web/src/components/ThreadStatusIndicators.tsx
requires `gitStatus.refName === thread.branch`, so those threads permanently lose their
PR badge even though the server returns the PR in `gitStatus.pr`.

Fix by syncing the stored branch, not by reverting the check:

1. Find where thread git state is refreshed after a turn — start at
   apps/server/src/orchestration/Layers/CheckpointReactor.ts, which already refreshes
   local git status, and apps/server/src/vcs/VcsStatusBroadcaster.ts, which owns the
   per-cwd status stream.
2. When a thread has `worktreePath !== null` and the observed HEAD ref for that worktree
   differs from the thread's stored branch, persist the observed ref as the thread's
   branch. Follow the existing orchestration event + projection pattern used for other
   projection_threads columns (grep for how `worktree_path` and `settled_at` are written)
   rather than writing SQL directly.
3. Only sync for dedicated worktrees. Threads on a shared local checkout must keep their
   stored branch — the shared HEAD is not owned by the thread.
4. Ignore detached HEAD (`rev-parse --abbrev-ref HEAD` returning "HEAD") and leave the
   stored branch alone.
5. Leave `resolveThreadPr` as-is. #4460's intent (label and PR must describe the same
   branch) stays satisfied once the stored branch tracks the live one.

Tests: extend apps/server/src/orchestration/Layers/CheckpointReactor.test.ts (or the
broadcaster tests) with cases for drifted worktree branch, shared-checkout thread that
must NOT sync, and detached HEAD. Run `vp check` and the focused server test files.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions