Skip to content

fix(code-review): base local diff on the branch fork point, not a stale ref - #181

Merged
wongk merged 4 commits into
mainfrom
fix/code-review-diff-base
Jul 16, 2026
Merged

fix(code-review): base local diff on the branch fork point, not a stale ref#181
wongk merged 4 commits into
mainfrom
fix/code-review-diff-base

Conversation

@wongk

@wongk wongk commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Problem

Local branch review hardcoded its diff scope to main...HEAD. That main is the local branch ref, which every worktree of a clone shares — so a worktree inherits whatever commit the primary checkout last left main on. Because A...B diffs from merge-base(A, B), a local main sitting behind the branch's fork point drags the merge base backwards and folds every commit that landed on the base in between into the review diff. The result: unrelated changes appear in the review whenever the worktree's main has advanced (or lagged) past the local main.

Why not just use origin/main

Preferring origin/main unconditionally only mirrors the bug. origin/<base> lags whenever the base has unpushed local commits; branch off those and the fork point is ahead of the remote ref, so it folds the unpushed base commits in instead. Both directions are reproducible:

Scenario main...HEAD origin/main...HEAD
Local main stale (the reported bug) wrong — pulls in unrelated landed commits correct
Unpushed commits on main correct wrong — pulls in the unpushed commits

Fix

The base is now chosen per run between the local ref and origin/<base>. Both merge bases are ancestors of HEAD along the base branch, so the ref producing the later merge base is the true fork point — correct under either kind of staleness. A repo with no remote has no second view and keeps its local ref (remote-less reviews unchanged). No git fetch is added, so this stays offline-safe.

Two things came along:

  • Default-branch detection. The base branch is now detected (origin/HEAD, then origin/main/origin/master, then local names, falling back to main) instead of assumed to be main, so master-default repos resolve correctly. --base-ref-override runs through the same selection.
  • fetch-intent was reading commit subjects off the same stale base, attributing other people's landed commits to the branch under review; it now reads from the fork point too.

Tests

TestResolveDiffBase builds real hermetic git repos (the pre-existing TestResolveScope tests asserted literal strings and were silently ambient-dependent). Mutation-checked both directions: reverting to the old local-ref behavior fails 6 tests; the naive always-prefer-origin fix fails the unpushed-commits test. Full gate green — 2088 passed, ruff + pyright clean. The seven byte-parity golden prefix fixtures pass unchanged.

Note: the last two commits address a self-review (findings from cr-64914, which reviewed this branch using the fixed base resolution) — a hallucinated subcommand name in a docstring, a stale (origin/main) in /start help, and moving the shared git-fixture runner into conftest.py per convention.

plugins/code-review version bumped 3.6.0 → 3.6.1.

🤖 Generated with Claude Code

wongk and others added 4 commits July 15, 2026 16:08
…le ref

Local branch review hardcoded its diff scope to `main...HEAD`. That `main`
is the local branch ref, which every worktree of a clone shares, so a
worktree inherits whatever commit the primary checkout last left it on.
Since `A...B` diffs from `merge-base(A, B)`, a lagging local ref drags the
merge base back past the branch's real fork point and folds unrelated
landed commits into the review diff.

Preferring `origin/<base>` unconditionally would only mirror the bug: that
ref lags whenever the base has unpushed local commits, and branching off
those puts the fork point ahead of it. Both merge bases are ancestors of
HEAD along the base branch, so pick the ref producing the later one — it is
the true fork point under either kind of staleness. A remote-less repo has
no second view and keeps its local ref.

The base branch is now detected (origin/HEAD, then main/master) rather than
assumed to be `main`, so master-default repos resolve correctly too, and
fetch-intent reads commit subjects from the same fork point instead of
attributing someone else's landed commit to the branch under review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document the fork-point base selection and default-branch detection. The
README's scope table, --base flag, and examples described the old
hardcoded `main` base; a new Base ref resolution section covers detection
and the later-merge-base rule.

Also corrects README staleness surfaced by the inventory check: the
architecture tree omitted the skills/ directory, three of four commands,
four of seven reviewer prompts, and scripts/, and the verifier-stats
section named an output artifact as GitHub mode's presenter.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three findings from the cr-64914 review, all confirmed against the code:

- _resolve_default_base_ref's docstring named a `compute-cache-keys`
  subcommand that does not exist; the consumer that origin-qualifies
  base_ref is `compute-hashes` (cmd_compute_hashes).
- /start's help hardcoded the base as `(origin/main)`, contradicting the
  detection and local-ref fallback this same change added.
- The hermetic git fixture runner was reached by importing a private
  helper out of prefix_golden_harness. It is shared test env setup used by
  two modules now, so it moves to conftest.py as GIT_IDENTITY_ENV +
  git_fixture and both modules import it from there. The harness's
  deliberate duplication is its independent walk wrapper (the A/B parity
  oracle), not its git plumbing, so sharing this does not weaken it — the
  seven byte-parity golden fixtures still pass unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Coverage previously exercised the two staleness kinds separately (stale
local ref; unpushed local commits). When local main and origin/main
diverge, both happen at once: the branch is cut from one of the two tips,
and _base_rev must pick that tip. Two complementary tests — branch off the
local tip, branch off the remote tip — each pinned by mutation checks: an
always-local base fails the off-remote case, always-origin fails the
off-local case, only the per-run later-merge-base selection passes both.

The v3.6.1 changelog bullet gains one clause stating the divergence case
is handled, since that is a real property of the shipped selection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@wongk
wongk merged commit 9e923ed into main Jul 16, 2026
5 checks passed
@wongk
wongk deleted the fix/code-review-diff-base branch July 16, 2026 14:07
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