Skip to content

Commit dc1f75f

Browse files
authored
fix(branch-selector): self-heal a stale cached default branch
Four reviewers (qa-swarm's paul + xp, plus the org Codex and veria-ai bots) independently flagged that a stale cached default branch never self-corrects: the picker auto-selects the cached "trunk", and the existing effect's `!selectedBranch` guard then blocks re-selection once the live default arrives, so a task could start on a branch that is no longer the default. Track the branch we auto-selected in a ref and re-select when the default changes out from under our own auto-pick — while never clobbering a branch the user chose deliberately. Adds tests for both the self-heal and the user-choice-preserved paths, and updates the TaskInput comment to match. Generated-By: PostHog Code Task-Id: 562fb90f-d823-425d-b53c-5f482b55a8b2
1 parent 3259867 commit dc1f75f

3 files changed

Lines changed: 94 additions & 5 deletions

File tree

packages/ui/src/features/git-interaction/components/BranchSelector.test.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,84 @@ describe("BranchSelector cloud mode", () => {
124124
expect(screen.queryByRole("option", { name: "main" })).toBeNull();
125125
});
126126

127+
it("re-selects the default when a stale cached default is replaced by the live one", () => {
128+
const onBranchSelect = vi.fn();
129+
const { rerender } = renderInTheme(
130+
<BranchSelector
131+
repoPath="owner/repo"
132+
currentBranch={null}
133+
defaultBranch="master"
134+
workspaceMode="cloud"
135+
cloudBranches={[]}
136+
cloudBranchesLoading={true}
137+
cloudSearchQuery=""
138+
selectedBranch={null}
139+
onBranchSelect={onBranchSelect}
140+
onCloudSearchChange={vi.fn()}
141+
/>,
142+
);
143+
144+
// Auto-selected the (stale) cached default.
145+
expect(onBranchSelect).toHaveBeenLastCalledWith("master");
146+
147+
// Parent commits that selection; then the live default arrives differing.
148+
rerender(
149+
<Theme>
150+
<BranchSelector
151+
repoPath="owner/repo"
152+
currentBranch={null}
153+
defaultBranch="main"
154+
workspaceMode="cloud"
155+
cloudBranches={["main"]}
156+
cloudBranchesLoading={false}
157+
cloudSearchQuery=""
158+
selectedBranch="master"
159+
onBranchSelect={onBranchSelect}
160+
onCloudSearchChange={vi.fn()}
161+
/>
162+
</Theme>,
163+
);
164+
165+
expect(onBranchSelect).toHaveBeenLastCalledWith("main");
166+
});
167+
168+
it("does not override a branch the user picked when the default later changes", () => {
169+
const onBranchSelect = vi.fn();
170+
const { rerender } = renderInTheme(
171+
<BranchSelector
172+
repoPath="owner/repo"
173+
currentBranch={null}
174+
defaultBranch="master"
175+
workspaceMode="cloud"
176+
cloudBranches={["master", "feature-x"]}
177+
cloudBranchesLoading={false}
178+
cloudSearchQuery=""
179+
selectedBranch="feature-x"
180+
onBranchSelect={onBranchSelect}
181+
onCloudSearchChange={vi.fn()}
182+
/>,
183+
);
184+
185+
rerender(
186+
<Theme>
187+
<BranchSelector
188+
repoPath="owner/repo"
189+
currentBranch={null}
190+
defaultBranch="main"
191+
workspaceMode="cloud"
192+
cloudBranches={["main", "feature-x"]}
193+
cloudBranchesLoading={false}
194+
cloudSearchQuery=""
195+
selectedBranch="feature-x"
196+
onBranchSelect={onBranchSelect}
197+
onCloudSearchChange={vi.fn()}
198+
/>
199+
</Theme>,
200+
);
201+
202+
expect(onBranchSelect).not.toHaveBeenCalled();
203+
});
204+
127205
it("surfaces the 'Use input as branch name' action when the typed value is new", async () => {
128206
const user = userEvent.setup();
129207
renderInTheme(

packages/ui/src/features/git-interaction/components/BranchSelector.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,19 @@ export function BranchSelector({
144144
const isSelectionOnly = workspaceMode === "worktree" || isCloudMode;
145145
const displayedBranch = isSelectionOnly ? selectedBranch : currentBranch;
146146

147+
// The branch we auto-selected, so we can tell our own pick apart from one the
148+
// user made. Lets us correct a stale default (e.g. a cached "trunk" that the
149+
// live list later contradicts) without ever clobbering a deliberate choice.
150+
const autoSelectedBranchRef = useRef<string | null>(null);
147151
useEffect(() => {
148-
if (isSelectionOnly && defaultBranch && !selectedBranch && onBranchSelect) {
152+
if (!isSelectionOnly || !defaultBranch || !onBranchSelect) return;
153+
// Adopt the default when nothing is selected yet, or when the default has
154+
// changed out from under a value we ourselves auto-selected — but leave a
155+
// user's own selection alone.
156+
const selectionIsOurs =
157+
!selectedBranch || selectedBranch === autoSelectedBranchRef.current;
158+
if (selectionIsOurs && selectedBranch !== defaultBranch) {
159+
autoSelectedBranchRef.current = defaultBranch;
149160
onBranchSelect(defaultBranch);
150161
}
151162
}, [isSelectionOnly, defaultBranch, selectedBranch, onBranchSelect]);

packages/ui/src/features/task-detail/components/TaskInput.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -435,10 +435,10 @@ export function TaskInput({
435435
const liveCloudDefaultBranch = cloudBranchData?.defaultBranch ?? null;
436436
// Serve the persisted default branch until the live list resolves, so the
437437
// majority "start on trunk" case pre-selects trunk with zero wait on a cold
438-
// start. The cached value is best-effort: `cloudDefaultBranch` switches to the
439-
// live value the moment it arrives, but the picker's auto-select only fires
440-
// while nothing is selected — so a default branch renamed since it was cached
441-
// (rare) would leave the seeded name selected until the user picks another.
438+
// start. The cached value is best-effort: if it's stale (a default branch
439+
// renamed since it was cached), `cloudDefaultBranch` switches to the live
440+
// value on arrival and BranchSelector re-selects it — as long as the user
441+
// hasn't picked a branch of their own in the meantime.
442442
const cloudDefaultBranch =
443443
liveCloudDefaultBranch ??
444444
(selectedCloudRepository

0 commit comments

Comments
 (0)