fix(server): stop attaching a base branch's PR to branches cut from it - #8210

Open
AyushKaithwas wants to merge 4 commits into
pingdotgg:mainfrom
AyushKaithwas:fix/status-pr-upstream-base
Open

fix(server): stop attaching a base branch's PR to branches cut from it#8210
AyushKaithwas wants to merge 4 commits into
pingdotgg:mainfrom
AyushKaithwas:fix/status-pr-upstream-base

Conversation

@AyushKaithwas

@AyushKaithwasAyushKaithwas commented Aug 25, 2026

Copy link
Copy Markdown

Fixes#8209.

A branch created with git checkout -b feature origin/dev tracks its base, and prLookupCache 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 in a repo that integrates through a non-default branch every thread cut from dev showed dev'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. resolveBaseBranch already 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-atom cannot name the local branch effect-atom and leaves upstream/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 on main), plus vp lint and tsgo --noEmit for 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-dev on origin/dev) by tightening when GitManager runs GitHub PR lookup during status.

prLookupCache no 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, so status.pr stays null and gh pr list is not called.

Three new tests cover non-default upstreams, hierarchical ref tails (v2 vs release/v2), and suffix collisions (feature/dev vs dev).

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.prLookupCache

  • Reworks the PR lookup skip logic in the prLookupCache loader so branches tracking a differently-named upstream ref (e.g. feature/from-dev tracking origin/dev) no longer inherit the upstream's PR.
  • Introduces 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.
  • Removes defaultBranch from the prLookupCacheKey composition; cache entries are now keyed on [cwd, branch, upstreamRef, epoch] and shared across different default-branch values.
  • Adds three tests in GitManager.test.ts covering non-default upstream, hierarchical-base tail match, and base-as-tail scenarios.
  • Behavioral Change: branches that previously received a PR from their upstream (when the upstream head was the default branch) will now show status.pr as null; review the localBranchIsAliasOfHead condition in GitManager.ts if legitimate alias branches stop resolving PRs.

Macroscope summarized 3ea8882.

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.
@coderabbitai

coderabbitaiBot commented Aug 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 92857433-44c5-4aed-80c7-fc48a3ce98e5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Aug 25, 2026
Comment threadapps/server/src/git/GitManager.ts Outdated
// 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}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumgit/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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment threadapps/server/src/git/GitManager.ts
@macroscopeapp

macroscopeappBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

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.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment threadapps/server/src/git/GitManager.ts Outdated
AyushKaithwasand others added 2 commits August 26, 2026 03:12
… 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.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Thread shows the base branch's PR when the branch tracks a non-default base

1 participant

@AyushKaithwas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(server): stop attaching a base branch's PR to branches cut from it - #8210

Open
AyushKaithwas wants to merge 4 commits into
pingdotgg:mainfrom
AyushKaithwas:fix/status-pr-upstream-base
Open

fix(server): stop attaching a base branch's PR to branches cut from it#8210
AyushKaithwas wants to merge 4 commits into
pingdotgg:mainfrom
AyushKaithwas:fix/status-pr-upstream-base

Conversation

@AyushKaithwas

@AyushKaithwasAyushKaithwas commented Aug 25, 2026

Copy link
Copy Markdown

Fixes#8209.

A branch created with git checkout -b feature origin/dev tracks its base, and prLookupCache 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 in a repo that integrates through a non-default branch every thread cut from dev showed dev'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. resolveBaseBranch already 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-atom cannot name the local branch effect-atom and leaves upstream/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 on main), plus vp lint and tsgo --noEmit for 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-dev on origin/dev) by tightening when GitManager runs GitHub PR lookup during status.

prLookupCache no 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, so status.pr stays null and gh pr list is not called.

Three new tests cover non-default upstreams, hierarchical ref tails (v2 vs release/v2), and suffix collisions (feature/dev vs dev).

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.prLookupCache

  • Reworks the PR lookup skip logic in the prLookupCache loader so branches tracking a differently-named upstream ref (e.g. feature/from-dev tracking origin/dev) no longer inherit the upstream's PR.
  • Introduces 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.
  • Removes defaultBranch from the prLookupCacheKey composition; cache entries are now keyed on [cwd, branch, upstreamRef, epoch] and shared across different default-branch values.
  • Adds three tests in GitManager.test.ts covering non-default upstream, hierarchical-base tail match, and base-as-tail scenarios.
  • Behavioral Change: branches that previously received a PR from their upstream (when the upstream head was the default branch) will now show status.pr as null; review the localBranchIsAliasOfHead condition in GitManager.ts if legitimate alias branches stop resolving PRs.

Macroscope summarized 3ea8882.

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.
@coderabbitai

coderabbitaiBot commented Aug 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 92857433-44c5-4aed-80c7-fc48a3ce98e5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Aug 25, 2026
Comment threadapps/server/src/git/GitManager.ts Outdated
// 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}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumgit/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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment threadapps/server/src/git/GitManager.ts
@macroscopeapp

macroscopeappBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

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.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment threadapps/server/src/git/GitManager.ts Outdated
AyushKaithwasand others added 2 commits August 26, 2026 03:12
… 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.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Thread shows the base branch's PR when the branch tracks a non-default base

1 participant

@AyushKaithwas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(server): stop attaching a base branch's PR to branches cut from it - #8210

Open
AyushKaithwas wants to merge 4 commits into
pingdotgg:mainfrom
AyushKaithwas:fix/status-pr-upstream-base
Open

fix(server): stop attaching a base branch's PR to branches cut from it#8210
AyushKaithwas wants to merge 4 commits into
pingdotgg:mainfrom
AyushKaithwas:fix/status-pr-upstream-base

Conversation

@AyushKaithwas

@AyushKaithwasAyushKaithwas commented Aug 25, 2026

Copy link
Copy Markdown

Fixes#8209.

A branch created with git checkout -b feature origin/dev tracks its base, and prLookupCache 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 in a repo that integrates through a non-default branch every thread cut from dev showed dev'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. resolveBaseBranch already 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-atom cannot name the local branch effect-atom and leaves upstream/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 on main), plus vp lint and tsgo --noEmit for 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-dev on origin/dev) by tightening when GitManager runs GitHub PR lookup during status.

prLookupCache no 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, so status.pr stays null and gh pr list is not called.

Three new tests cover non-default upstreams, hierarchical ref tails (v2 vs release/v2), and suffix collisions (feature/dev vs dev).

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.prLookupCache

  • Reworks the PR lookup skip logic in the prLookupCache loader so branches tracking a differently-named upstream ref (e.g. feature/from-dev tracking origin/dev) no longer inherit the upstream's PR.
  • Introduces 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.
  • Removes defaultBranch from the prLookupCacheKey composition; cache entries are now keyed on [cwd, branch, upstreamRef, epoch] and shared across different default-branch values.
  • Adds three tests in GitManager.test.ts covering non-default upstream, hierarchical-base tail match, and base-as-tail scenarios.
  • Behavioral Change: branches that previously received a PR from their upstream (when the upstream head was the default branch) will now show status.pr as null; review the localBranchIsAliasOfHead condition in GitManager.ts if legitimate alias branches stop resolving PRs.

Macroscope summarized 3ea8882.

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.
@coderabbitai

coderabbitaiBot commented Aug 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 92857433-44c5-4aed-80c7-fc48a3ce98e5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Aug 25, 2026
Comment threadapps/server/src/git/GitManager.ts Outdated
// 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}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumgit/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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment threadapps/server/src/git/GitManager.ts
@macroscopeapp

macroscopeappBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

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.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment threadapps/server/src/git/GitManager.ts Outdated
AyushKaithwasand others added 2 commits August 26, 2026 03:12
… 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.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Thread shows the base branch's PR when the branch tracks a non-default base

1 participant

@AyushKaithwas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(server): stop attaching a base branch's PR to branches cut from it - #8210

Open
AyushKaithwas wants to merge 4 commits into
pingdotgg:mainfrom
AyushKaithwas:fix/status-pr-upstream-base
Open

fix(server): stop attaching a base branch's PR to branches cut from it#8210
AyushKaithwas wants to merge 4 commits into
pingdotgg:mainfrom
AyushKaithwas:fix/status-pr-upstream-base

Conversation

@AyushKaithwas

@AyushKaithwasAyushKaithwas commented Aug 25, 2026

Copy link
Copy Markdown

Fixes#8209.

A branch created with git checkout -b feature origin/dev tracks its base, and prLookupCache 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 in a repo that integrates through a non-default branch every thread cut from dev showed dev'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. resolveBaseBranch already 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-atom cannot name the local branch effect-atom and leaves upstream/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 on main), plus vp lint and tsgo --noEmit for 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-dev on origin/dev) by tightening when GitManager runs GitHub PR lookup during status.

prLookupCache no 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, so status.pr stays null and gh pr list is not called.

Three new tests cover non-default upstreams, hierarchical ref tails (v2 vs release/v2), and suffix collisions (feature/dev vs dev).

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.prLookupCache

  • Reworks the PR lookup skip logic in the prLookupCache loader so branches tracking a differently-named upstream ref (e.g. feature/from-dev tracking origin/dev) no longer inherit the upstream's PR.
  • Introduces 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.
  • Removes defaultBranch from the prLookupCacheKey composition; cache entries are now keyed on [cwd, branch, upstreamRef, epoch] and shared across different default-branch values.
  • Adds three tests in GitManager.test.ts covering non-default upstream, hierarchical-base tail match, and base-as-tail scenarios.
  • Behavioral Change: branches that previously received a PR from their upstream (when the upstream head was the default branch) will now show status.pr as null; review the localBranchIsAliasOfHead condition in GitManager.ts if legitimate alias branches stop resolving PRs.

Macroscope summarized 3ea8882.

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.
@coderabbitai

coderabbitaiBot commented Aug 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 92857433-44c5-4aed-80c7-fc48a3ce98e5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Aug 25, 2026
Comment threadapps/server/src/git/GitManager.ts Outdated
// 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}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumgit/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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment threadapps/server/src/git/GitManager.ts
@macroscopeapp

macroscopeappBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

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.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment threadapps/server/src/git/GitManager.ts Outdated
AyushKaithwasand others added 2 commits August 26, 2026 03:12
… 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.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Thread shows the base branch's PR when the branch tracks a non-default base

1 participant

@AyushKaithwas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(server): stop attaching a base branch's PR to branches cut from it - #8210

Open
AyushKaithwas wants to merge 4 commits into
pingdotgg:mainfrom
AyushKaithwas:fix/status-pr-upstream-base
Open

fix(server): stop attaching a base branch's PR to branches cut from it#8210
AyushKaithwas wants to merge 4 commits into
pingdotgg:mainfrom
AyushKaithwas:fix/status-pr-upstream-base

Conversation

@AyushKaithwas

@AyushKaithwasAyushKaithwas commented Aug 25, 2026

Copy link
Copy Markdown

Fixes#8209.

A branch created with git checkout -b feature origin/dev tracks its base, and prLookupCache 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 in a repo that integrates through a non-default branch every thread cut from dev showed dev'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. resolveBaseBranch already 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-atom cannot name the local branch effect-atom and leaves upstream/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 on main), plus vp lint and tsgo --noEmit for 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-dev on origin/dev) by tightening when GitManager runs GitHub PR lookup during status.

prLookupCache no 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, so status.pr stays null and gh pr list is not called.

Three new tests cover non-default upstreams, hierarchical ref tails (v2 vs release/v2), and suffix collisions (feature/dev vs dev).

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.prLookupCache

  • Reworks the PR lookup skip logic in the prLookupCache loader so branches tracking a differently-named upstream ref (e.g. feature/from-dev tracking origin/dev) no longer inherit the upstream's PR.
  • Introduces 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.
  • Removes defaultBranch from the prLookupCacheKey composition; cache entries are now keyed on [cwd, branch, upstreamRef, epoch] and shared across different default-branch values.
  • Adds three tests in GitManager.test.ts covering non-default upstream, hierarchical-base tail match, and base-as-tail scenarios.
  • Behavioral Change: branches that previously received a PR from their upstream (when the upstream head was the default branch) will now show status.pr as null; review the localBranchIsAliasOfHead condition in GitManager.ts if legitimate alias branches stop resolving PRs.

Macroscope summarized 3ea8882.

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.
@coderabbitai

coderabbitaiBot commented Aug 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 92857433-44c5-4aed-80c7-fc48a3ce98e5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Aug 25, 2026
Comment threadapps/server/src/git/GitManager.ts Outdated
// 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}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumgit/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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment threadapps/server/src/git/GitManager.ts
@macroscopeapp

macroscopeappBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

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.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment threadapps/server/src/git/GitManager.ts Outdated
AyushKaithwasand others added 2 commits August 26, 2026 03:12
… 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.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Thread shows the base branch's PR when the branch tracks a non-default base

1 participant

@AyushKaithwas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(server): stop attaching a base branch's PR to branches cut from it - #8210

Open
AyushKaithwas wants to merge 4 commits into
pingdotgg:mainfrom
AyushKaithwas:fix/status-pr-upstream-base
Open

fix(server): stop attaching a base branch's PR to branches cut from it#8210
AyushKaithwas wants to merge 4 commits into
pingdotgg:mainfrom
AyushKaithwas:fix/status-pr-upstream-base

Conversation

@AyushKaithwas

@AyushKaithwasAyushKaithwas commented Aug 25, 2026

Copy link
Copy Markdown

Fixes#8209.

A branch created with git checkout -b feature origin/dev tracks its base, and prLookupCache 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 in a repo that integrates through a non-default branch every thread cut from dev showed dev'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. resolveBaseBranch already 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-atom cannot name the local branch effect-atom and leaves upstream/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 on main), plus vp lint and tsgo --noEmit for 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-dev on origin/dev) by tightening when GitManager runs GitHub PR lookup during status.

prLookupCache no 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, so status.pr stays null and gh pr list is not called.

Three new tests cover non-default upstreams, hierarchical ref tails (v2 vs release/v2), and suffix collisions (feature/dev vs dev).

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.prLookupCache

  • Reworks the PR lookup skip logic in the prLookupCache loader so branches tracking a differently-named upstream ref (e.g. feature/from-dev tracking origin/dev) no longer inherit the upstream's PR.
  • Introduces 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.
  • Removes defaultBranch from the prLookupCacheKey composition; cache entries are now keyed on [cwd, branch, upstreamRef, epoch] and shared across different default-branch values.
  • Adds three tests in GitManager.test.ts covering non-default upstream, hierarchical-base tail match, and base-as-tail scenarios.
  • Behavioral Change: branches that previously received a PR from their upstream (when the upstream head was the default branch) will now show status.pr as null; review the localBranchIsAliasOfHead condition in GitManager.ts if legitimate alias branches stop resolving PRs.

Macroscope summarized 3ea8882.

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.
@coderabbitai

coderabbitaiBot commented Aug 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 92857433-44c5-4aed-80c7-fc48a3ce98e5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Aug 25, 2026
Comment threadapps/server/src/git/GitManager.ts Outdated
// 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}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumgit/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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment threadapps/server/src/git/GitManager.ts
@macroscopeapp

macroscopeappBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

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.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment threadapps/server/src/git/GitManager.ts Outdated
AyushKaithwasand others added 2 commits August 26, 2026 03:12
… 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.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Thread shows the base branch's PR when the branch tracks a non-default base

1 participant

@AyushKaithwas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(server): stop attaching a base branch's PR to branches cut from it - #8210

Open
AyushKaithwas wants to merge 4 commits into
pingdotgg:mainfrom
AyushKaithwas:fix/status-pr-upstream-base
Open

fix(server): stop attaching a base branch's PR to branches cut from it#8210
AyushKaithwas wants to merge 4 commits into
pingdotgg:mainfrom
AyushKaithwas:fix/status-pr-upstream-base

Conversation

@AyushKaithwas

@AyushKaithwasAyushKaithwas commented Aug 25, 2026

Copy link
Copy Markdown

Fixes#8209.

A branch created with git checkout -b feature origin/dev tracks its base, and prLookupCache 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 in a repo that integrates through a non-default branch every thread cut from dev showed dev'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. resolveBaseBranch already 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-atom cannot name the local branch effect-atom and leaves upstream/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 on main), plus vp lint and tsgo --noEmit for 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-dev on origin/dev) by tightening when GitManager runs GitHub PR lookup during status.

prLookupCache no 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, so status.pr stays null and gh pr list is not called.

Three new tests cover non-default upstreams, hierarchical ref tails (v2 vs release/v2), and suffix collisions (feature/dev vs dev).

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.prLookupCache

  • Reworks the PR lookup skip logic in the prLookupCache loader so branches tracking a differently-named upstream ref (e.g. feature/from-dev tracking origin/dev) no longer inherit the upstream's PR.
  • Introduces 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.
  • Removes defaultBranch from the prLookupCacheKey composition; cache entries are now keyed on [cwd, branch, upstreamRef, epoch] and shared across different default-branch values.
  • Adds three tests in GitManager.test.ts covering non-default upstream, hierarchical-base tail match, and base-as-tail scenarios.
  • Behavioral Change: branches that previously received a PR from their upstream (when the upstream head was the default branch) will now show status.pr as null; review the localBranchIsAliasOfHead condition in GitManager.ts if legitimate alias branches stop resolving PRs.

Macroscope summarized 3ea8882.

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.
@coderabbitai

coderabbitaiBot commented Aug 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 92857433-44c5-4aed-80c7-fc48a3ce98e5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Aug 25, 2026
Comment threadapps/server/src/git/GitManager.ts Outdated
// 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}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumgit/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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment threadapps/server/src/git/GitManager.ts
@macroscopeapp

macroscopeappBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

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.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment threadapps/server/src/git/GitManager.ts Outdated
AyushKaithwasand others added 2 commits August 26, 2026 03:12
… 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.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Thread shows the base branch's PR when the branch tracks a non-default base

1 participant

@AyushKaithwas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(server): stop attaching a base branch's PR to branches cut from it - #8210

Open
AyushKaithwas wants to merge 4 commits into
pingdotgg:mainfrom
AyushKaithwas:fix/status-pr-upstream-base
Open

fix(server): stop attaching a base branch's PR to branches cut from it#8210
AyushKaithwas wants to merge 4 commits into
pingdotgg:mainfrom
AyushKaithwas:fix/status-pr-upstream-base

Conversation

@AyushKaithwas

@AyushKaithwasAyushKaithwas commented Aug 25, 2026

Copy link
Copy Markdown

Fixes#8209.

A branch created with git checkout -b feature origin/dev tracks its base, and prLookupCache 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 in a repo that integrates through a non-default branch every thread cut from dev showed dev'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. resolveBaseBranch already 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-atom cannot name the local branch effect-atom and leaves upstream/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 on main), plus vp lint and tsgo --noEmit for 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-dev on origin/dev) by tightening when GitManager runs GitHub PR lookup during status.

prLookupCache no 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, so status.pr stays null and gh pr list is not called.

Three new tests cover non-default upstreams, hierarchical ref tails (v2 vs release/v2), and suffix collisions (feature/dev vs dev).

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.prLookupCache

  • Reworks the PR lookup skip logic in the prLookupCache loader so branches tracking a differently-named upstream ref (e.g. feature/from-dev tracking origin/dev) no longer inherit the upstream's PR.
  • Introduces 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.
  • Removes defaultBranch from the prLookupCacheKey composition; cache entries are now keyed on [cwd, branch, upstreamRef, epoch] and shared across different default-branch values.
  • Adds three tests in GitManager.test.ts covering non-default upstream, hierarchical-base tail match, and base-as-tail scenarios.
  • Behavioral Change: branches that previously received a PR from their upstream (when the upstream head was the default branch) will now show status.pr as null; review the localBranchIsAliasOfHead condition in GitManager.ts if legitimate alias branches stop resolving PRs.

Macroscope summarized 3ea8882.

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.
@coderabbitai

coderabbitaiBot commented Aug 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 92857433-44c5-4aed-80c7-fc48a3ce98e5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Aug 25, 2026
Comment threadapps/server/src/git/GitManager.ts Outdated
// 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}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Mediumgit/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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment threadapps/server/src/git/GitManager.ts
@macroscopeapp

macroscopeappBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

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.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment threadapps/server/src/git/GitManager.ts Outdated
AyushKaithwasand others added 2 commits August 26, 2026 03:12
… 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.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Thread shows the base branch's PR when the branch tracks a non-default base

1 participant

@AyushKaithwas