fix(server): stop attaching a base branch's PR to branches cut from it - #8210
fix(server): stop attaching a base branch's PR to branches cut from it#8210AyushKaithwas wants to merge 4 commits into
Conversation
A branch created with `git checkout -b feature origin/dev` tracks its base, and the status PR lookup reads the upstream ref as the branch's published head. The guard against that only fired when the upstream was the repository default branch, so a repo that integrates through a non-default branch showed that branch's newest release PR on every thread cut from it. Treat any upstream whose name differs from the local branch as the base, which is how `resolveBaseBranch` already reads it when opening a PR. Branches that track a remote ref under a git-mangled local name (a remote whose own name contains a slash) keep their lookup.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| // cannot name it "effect-atom", so it keeps "upstream/effect-atom". Its | ||
| // upstream is still its published head. | ||
| const localBranchTracksUpstreamHead = | ||
| details.upstreamRef !== null && details.upstreamRef.endsWith(`/${details.branch}`); |
There was a problem hiding this comment.
🟡 Medium git/GitManager.ts:953
Status suppresses a real PR badge after git branch -m old-name new-name because the renamed branch still tracks origin/old-name, causing resolveBranchHeadContext to return { latest: null }. Conversely, endsWith('/feature') misclassifies origin/team/feature as the published head for local feature, so status can display an unrelated PR from the base branch. Replace this heuristic with logic that distinguishes renamed branches from base-tracking branches and compares the remote name separately from the slash-containing remote branch name.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/git/GitManager.ts around line 953:
Status suppresses a real PR badge after `git branch -m old-name new-name` because the renamed branch still tracks `origin/old-name`, causing `resolveBranchHeadContext` to return `{ latest: null }`. Conversely, `endsWith('/feature')` misclassifies `origin/team/feature` as the published head for local `feature`, so status can display an unrelated PR from the base branch. Replace this heuristic with logic that distinguishes renamed branches from base-tracking branches and compares the remote name separately from the slash-containing remote branch name.
Evidence trail:
Commit 16d4313: apps/server/src/git/GitManager.ts:939-973, 1187-1258, 1311-1379; apps/server/src/vcs/GitVcsDriverCore.ts:992-1011, 1485-1531; apps/server/src/git/remoteRefs.ts:44-72. Tests: apps/server/src/git/GitManager.test.ts:1273-1387, 1460-1536. Git documentation: https://git-scm.com/docs/git-branch. Commands: git diff MERGE_BASE REVIEWED_COMMIT -- apps/server/src/git/GitManager.ts; git show 16d4313.
There was a problem hiding this comment.
The second half (origin/team/feature misclassified as the published head of local feature) was real and is fixed in 1d1ca06: the suffix comparison now runs against the parsed headContext.headBranch in the alias direction only, with a regression test.
The rename half is inherent ambiguity, not something this heuristic can resolve: after git branch -m old new, branch.new.merge is refs/heads/old, byte-identical to a checkout cut from old. No config or commit-graph signal distinguishes them (a fresh cut equals its base exactly; a renamed branch with local work is ahead of its upstream, and so is a stale base). Given the tie, hiding the badge until the next push is the safer failure mode: attaching the wrong PR auto-settles the thread and survives checkout switches because merged PRs are retained, which is the bug this PR fixes. The badge comes back on git push, since push updates old on the remote and that head is the PR's. On main today the rename case only worked when the upstream was non-default; it was already suppressed for default-branch upstreams by the previous guard.
There was a problem hiding this comment.
Sorry, I'm unable to act on this request because you do not have permissions within this repository.
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This is a narrowly scoped fix to prevent inherited pull-request metadata, with targeted regression coverage. The updated branch heuristic still has an unresolved tradeoff where renamed branches may lose a legitimate PR badge, so the behavior warrants human review. Not approved because:
Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
`upstreamRef.endsWith("/" + branch)` also matched a branch cut from a
hierarchical base whose tail equals the branch name (local v2 tracking
origin/release/v2), so those kept inheriting the base's PR. The alias
suffix runs the other way: the git-mangled local name ends with the
parsed head branch. Compare against headContext.headBranch directly.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1d1ca06. Configure here.
… ref A branch ending in its head's name was still counted as an alias, so feature/dev cut from origin/dev kept inheriting dev's PR. A git-mangled alias satisfies both suffix directions: the local name ends with the parsed head and the upstream ref ends with the local name.

Fixes #8209.
A branch created with
git checkout -b feature origin/devtracks its base, andprLookupCachereads the upstream ref as the branch's published head. The guard against that only fired when the upstream was the repository default branch, so in a repo that integrates through a non-default branch every thread cut fromdevshoweddev's newest release PR, kept showing it after checkouts because that PR is merged, and settled on it.The upstream of a branch whose name differs from it is the branch's base.
resolveBaseBranchalready reads the same config that way when opening a PR, so this makes the status lookup agree with the create path instead of contradicting it.One same-repo case really does track a differently named head: a tracking checkout of a remote whose own name contains a slash, where
git checkout --track my-org/upstream/effect-atomcannot name the local brancheffect-atomand leavesupstream/effect-atom. Those keep their lookup by matching the local name against the tail of the upstream ref, and the existing test for them still passes. Cross-repo head contexts are untouched.Also drops the default branch from the PR lookup cache key, since nothing in the lookup reads it now.
No UI change beyond the wrong badge no longer appearing.
Verification:
vp test run apps/server/src/git/GitManager.test.ts(87 passed, including the new case, which fails onmain), plusvp lintandtsgo --noEmitfor the touched scope.Written by Claude Opus 5 (1M context) in T3 Code.
Note
Medium Risk
Changes git status PR resolution for many tracking setups; legitimate alias branches depend on the new suffix rules, but behavior now aligns with PR creation base resolution.
Overview
Fixes incorrect PR badges on feature branches that track an integration or release branch (e.g.
feature/from-devonorigin/dev) by tightening whenGitManagerruns GitHub PR lookup duringstatus.prLookupCacheno longer keys on the default branch. It skips lookup unless the local branch is treated as the same published head as its upstream—either the names match, or the git tracking-alias case applies (local ends with/<head>and upstream ends with/<local>). Otherwise the upstream is treated as the branch’s base, sostatus.prstaysnullandgh pr listis not called.Three new tests cover non-default upstreams, hierarchical ref tails (
v2vsrelease/v2), and suffix collisions (feature/devvsdev).Reviewed by Cursor Bugbot for commit 3ea8882. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Stop inheriting PR metadata from base branches in
GitManager.prLookupCacheprLookupCacheloader so branches tracking a differently-named upstream ref (e.g.feature/from-devtrackingorigin/dev) no longer inherit the upstream's PR.localBranchIsAliasOfHead, which is true when the local branch name equals the resolved head branch, or when the local branch ends with/<headBranch>and the upstream ref ends with/<localBranch>. Only alias branches still perform PR lookup.defaultBranchfrom theprLookupCacheKeycomposition; cache entries are now keyed on[cwd, branch, upstreamRef, epoch]and shared across different default-branch values.status.prasnull; review thelocalBranchIsAliasOfHeadcondition inGitManager.tsif legitimate alias branches stop resolving PRs.Macroscope summarized 3ea8882.