Skip to content

fix(server): name both refs in the base ref mismatch error#2161

Open
cleiter wants to merge 1 commit into
getpaseo:mainfrom
cleiter:fix/base-ref-mismatch-message
Open

fix(server): name both refs in the base ref mismatch error#2161
cleiter wants to merge 1 commit into
getpaseo:mainfrom
cleiter:fix/base-ref-mismatch-message

Conversation

@cleiter

@cleiter cleiter commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Linked issue

N/A

Type of change

  • Bug fix
  • New feature (with prior issue + design alignment)
  • Refactor / code improvement
  • Docs

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 ?? resolvedBaseRef and then
printed baseRef as "expected" against the caller's ref as "got". The guard only fires when
the caller passed a ref, so at the throw baseRef is the caller's ref — both sides were
the same string by construction, on every path. The value that actually differs, the stored
baseRefName from <gitdir>/paseo/worktree.json, was never shown.

The message is now built in one place and thrown by the four guards:

Base ref mismatch: stored main, requested other

Two deliberate choices worth flagging for review:

  • stored/requested, not expected/got. A caller's ref can be stale, but the
    stored 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.
  • The guards stay inline; only the message moved. One shared string is what stops the
    four copies drifting apart again, but folding the ifs into an assert helper would have
    hidden the firing condition from each call site and forced the helper to re-accept the
    null/undefined shapes. Building only the error keeps each throw's condition visible and
    narrows both refs to plain string before 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 with
baseRef: "other":

AssertionError: expected [Function] to throw error including 'Base ref mismatch: stored main, reque…'
                                                but got 'Base ref mismatch: expected other, got other'

Expected: "Base ref mismatch: stored main, requested other"
Received: "Base ref mismatch: expected other, got other"

expected other, got other is the bug reproduced: two different refs, one of them printed
twice. After the change that test passes and the message names both refs.

  • npx vitest run packages/server/src/utils/checkout-git.test.ts121 passed (120 existing + 1 new)
  • npm run typecheck — clean
  • npm run lint — 0 warnings, 0 errors
  • npm run format:check — clean

Checklist

  • One focused change. Unrelated cleanups split out.
  • npm run typecheck passes
  • npm run lint passes
  • npm run format ran (Biome)
  • UI changes include screenshots or video for every affected platform — n/a, no UI change
  • Tests added or updated where it made sense

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>
@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where the base-ref mismatch error always printed the caller's ref on both sides of the message, making the output read like "expected other, got other" instead of naming the stored ref that actually differs from the requested one. The fix extracts a single baseRefMismatchError helper that takes { stored, requested } and applies it at all four throw sites.

  • Bug fix: storedBaseRef is now shown as stored and the caller-supplied ref as requested, giving actionable information to the diagnoser.
  • Single source of truth: Moving the message string into one place ensures all four guards stay in sync and prevents future drift.
  • Test coverage: A new integration test creates a real worktree with baseBranch: "main", requests a diff with baseRef: "other", and asserts the exact error text, which was written first to reproduce the original bug.

Confidence Score: 5/5

Safe to merge — the change is a two-line message fix at four identical throw sites plus a new test that reproduces the original bug and verifies the corrected output.

The fix is mechanically correct at every site: storedBaseRef now maps to stored and the caller's ref maps to requested. The root cause is well-explained and clearly eliminated. The new test was written to fail before the fix and passes after.

No files require special attention.

Important Files Changed

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
Loading
%%{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
Loading

Reviews (1): Last reviewed commit: "fix(server): name both refs in the base ..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant