fix(server): name both refs in the base ref mismatch error#2161
Conversation
The base ref mismatch error printed the same value on both sides, so it always read "expected master, got master". All four throw sites computed `baseRef = compare.baseRef ?? resolvedBaseRef`, but the guard only fires when the caller passed a ref, so `baseRef` and the "got" value were the same string by construction. The value that actually differs -- the stored `baseRefName` from the worktree metadata -- was never shown, which left the error undiagnosable from the UI alone. Build the message in one place and have the four guards throw it, naming the refs `stored` and `requested`. The wording deliberately avoids "expected": a caller's ref can be stale, but the stored ref can equally be the wrong one, so the message states both facts and leaves the diagnosis open. The guards stay where they are. Only the message moves, so the condition that fires each throw is still visible at the call site, and both refs are narrowed to plain strings by the time the message is built. Throw conditions are unchanged; only the message text differs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
| Filename | Overview |
|---|---|
| packages/server/src/utils/checkout-git.ts | Adds baseRefMismatchError helper and updates all four throw sites to use it — each now correctly shows storedBaseRef as "stored" and the caller's ref as "requested" instead of printing the same value twice. |
| packages/server/src/utils/checkout-git.test.ts | Adds one integration test that exercises the mismatch guard through getCheckoutDiff with a real worktree; asserts the full error string to prevent future regression. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Caller supplies baseRef] --> B{storedBaseRef exists AND options.baseRef provided?}
B -- No --> C[Resolve best base ref and proceed normally]
B -- Yes --> D{options.baseRef === storedBaseRef?}
D -- Yes --> C
D -- No --> E[baseRefMismatchError stored: storedBaseRef, requested: options.baseRef]
E --> F[throw Error: 'Base ref mismatch: stored X, requested Y']
style E fill:#f96,color:#000
style F fill:#f66,color:#fff
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[Caller supplies baseRef] --> B{storedBaseRef exists AND options.baseRef provided?}
B -- No --> C[Resolve best base ref and proceed normally]
B -- Yes --> D{options.baseRef === storedBaseRef?}
D -- Yes --> C
D -- No --> E[baseRefMismatchError stored: storedBaseRef, requested: options.baseRef]
E --> F[throw Error: 'Base ref mismatch: stored X, requested Y']
style E fill:#f96,color:#000
style F fill:#f66,color:#fff
Reviews (1): Last reviewed commit: "fix(server): name both refs in the base ..." | Re-trigger Greptile
Linked issue
N/A
Type of change
What does this PR do
The base ref mismatch error printed the same ref on both sides, so it always read
Base ref mismatch: expected master, got master— which says nothing about what mismatched.All four throw sites computed
baseRef = compare.baseRef ?? resolvedBaseRefand thenprinted
baseRefas "expected" against the caller's ref as "got". The guard only fires whenthe caller passed a ref, so at the throw
baseRefis the caller's ref — both sides werethe same string by construction, on every path. The value that actually differs, the stored
baseRefNamefrom<gitdir>/paseo/worktree.json, was never shown.The message is now built in one place and thrown by the four guards:
Two deliberate choices worth flagging for review:
stored/requested, notexpected/got. A caller's ref can be stale, but thestored ref can equally be the wrong one — a worktree whose recorded base has drifted looks
exactly like this. The message states both facts and leaves the diagnosis open rather than
asserting either side is correct.
four copies drifting apart again, but folding the
ifs into an assert helper would havehidden the firing condition from each call site and forced the helper to re-accept the
null/undefinedshapes. Building only the error keeps each throw's condition visible andnarrows both refs to plain
stringbefore the message is built.Throw conditions are unchanged — same inputs still throw, only the text differs.
How did you verify it
Wrote the test first and watched it fail on the real message before changing any source.
Against a Paseo worktree whose stored base is
main, requesting a diff withbaseRef: "other":expected other, got otheris the bug reproduced: two different refs, one of them printedtwice. After the change that test passes and the message names both refs.
npx vitest run packages/server/src/utils/checkout-git.test.ts— 121 passed (120 existing + 1 new)npm run typecheck— cleannpm run lint— 0 warnings, 0 errorsnpm run format:check— cleanChecklist
npm run typecheckpassesnpm run lintpassesnpm run formatran (Biome)