Skip to content

fix(server): Changes counts only a fork branch's own work - #15

Merged
asonawalla merged 1 commit into
mainfrom
fix-changes-base-on-fork
Oct 18, 2026
Merged

asonawalla merged 1 commit into
mainfrom
fix-changes-base-on-fork

Conversation

@asonawalla

Copy link
Copy Markdown
Owner

The thread panel's Changes row showed +2,381 −1,165 for #14, whose diff is +58 −63. The base name (main) was only ever looked up on the primary remote or as a local branch. So a branch cut from fork/main was diffed against origin/main and counted the fork's 13 commits that upstream doesn't have. Even gh-merge-base=fork/main resolved to origin/main.

Summary

resolveBaseBranchForNoUpstream (apps/server/src/vcs/GitVcsDriverCore.ts) feeds the Changes row, the diff panel's Changes view, and the ahead count:

 name = gh-merge-base ?? primary remote's default ?? main ?? master
-return origin/<name> ?? local <name>
+if origin lacks <name> and local <name> exists
+  return local <name>
+copies = [origin/<name>, <remote local <name> tracks>/<name>]
+return the copy that leaves the fewest of the branch's commits outside it
Setup Base before Base after
One remote origin/main origin/main, plus one cheap for-each-ref
main tracks fork/main, branch cut from fork/main origin/main ✗ fork/main
Same repo, branch cut from origin/main origin/main origin/main
Same repo, on main itself origin/main ✗ fork/main
Checkout of a PR opened from its author's main origin/main origin/main: the author's remote is never compared

Only the remote the local base branch tracks counts. The push remote is excluded: in the common contributor setup (main tracks origin, pushes to a fork), counting it would leave Changes on main with no committed work after a push.

Evidence

  • Before: T3's real code on the PR fix(server): one runaway agent no longer freezes every agent #14 history, with origin/main and fork/main as they were in the screenshot:

    remove-agent-memory-limits {"baseRef":"origin/main","insertions":2381,"deletions":1165}
    

    After:

    remove-agent-memory-limits {"baseRef":"fork/main","insertions":58,"deletions":63}
    
  • Before: counts Changes from the copy of main a branch was cut from fails on the old code:

    × expected { baseRef: 'fork/main', insertions: 1 }
      actual   { baseRef: 'origin/main', insertions: 4 }   # 1 feature line + 3 fork-only lines
    

    An earlier version of this fix compared every remote. never counts Changes from a pull request author's copy of main fails on that version with { baseRef: 'author/main', insertions: 0 }.

    After: both pass. GitVcsDriverCore.test.ts passes 156/156 and VcsStatusBroadcaster.test.ts 27/27. The server typecheck, lint and format are clean.

  • GitManager.test.ts: passed 143/143 in most runs. Two tests with tight timeouts ("stops probing head selectors…" at 12 s, "does not reuse a cross-repo PR…" at 20 s) failed once while the Proxmox host's load average was about 300. In an A/B run under that load, the unchanged code also took 22.3 s on the first one, and the fix took 1.3–2.6 s.

Merge Danger

Door: two-way

Read-only base selection. It changes no stored state, so reverting restores the old numbers on the next status refresh.

Blast Radius: multi-remote

Repos with one remote get the same base as before. Repos whose local base branch tracks a second remote can get a different base in three places:

  • the Changes row
  • the diff panel's Changes view
  • aheadCount / aheadOfDefaultCount, which gate the Create PR button

Known limit: a fork main that already merged the branch through a merge commit has none of the branch's commits outside it, so it wins and Changes shows only uncommitted work. The old code does the same on origin. This fork squash-merges.

Not changed here: new worktrees start from the primary remote's default branch, and PR creation picks its base in GitManager.resolveBaseBranch.

Model and harness: Claude Opus 5.5 in Claude Code via T3 Code.

🤖 Generated with Claude Code

The thread panel's Changes row showed +2,381 -1,165 for a branch whose
diff was +58 -63. The base name ("main") was only ever looked up on the
primary remote or as a local branch, so a branch cut from fork/main was
diffed against origin/main and counted every commit the fork has that
upstream lacks. Even gh-merge-base=fork/main resolved to origin/main.

The base name is chosen as before. When the remote the local base
branch tracks also has it, as with a local main that follows a fork,
Changes uses whichever of the two copies the branch was cut from: the
one that leaves the fewest of its commits outside. Remotes fetched only
for someone else's pull request are never compared, and the local
branch still wins when the primary remote lacks the name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M labels Oct 18, 2026
@asonawalla
asonawalla merged commit 8e55e81 into main Oct 18, 2026
7 of 22 checks passed
@asonawalla
asonawalla deleted the fix-changes-base-on-fork branch October 18, 2026 01:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant