fix(vcs): resolve the default branch from the remote, not a stale origin/HEAD - #7394
AntoineArt wants to merge 2 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0c8e14b7426bd8fb30d23bbfd27a4e75233795f3. Configure here.
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This fix introduces network calls to probe remotes for default branch resolution (where none existed before), changing runtime behavior system-wide. Combined with the author being a first-time contributor, human review is recommended to validate the caching strategy and timeout implications. You can add or adjust custom eligibility rules. Learn more. |
c9c3486 to
92834ef
Compare
…gin/HEAD `refs/remotes/<remote>/HEAD` is written once, at clone time, and neither fetch nor pull ever moves it again. A project that changes its default branch afterwards leaves every existing clone pointing at the old one, so `isDefaultBranch` reports false for the branch that actually is default. That misclassification is load-bearing: `readRemoteStatus` only suppresses merged/closed change requests when `isDefaultBranch` is true, so a thread on a long-lived default branch such as `dev` binds to the last merged MR/PR from that branch and re-settles after every completed turn. Probe the remote with `ls-remote --symref` and fall back to the local snapshot when it is unreachable. The probe reuses the non-interactive env and timeout already used for background status fetches, and is cached for 30 minutes on success, 5 minutes when the remote could not be reached — the same 5 minutes when the probe times out or cannot spawn, so a degraded remote cannot turn every status read into a fresh probe. Refs pingdotgg#4970 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
92834ef to
1a414e4
Compare
|
Note: GPT-6 on behalf of shivam (@shivamhwp). The remote HEAD probe now runs on the local-status path too. Please refresh the remote default branch in the remote/background path and let local status consume the cached answer or local HEAD immediately. Keep the success cache and failure cooldown without making local updates await the probe. This handles stale default-branch detection, while #9443 addresses a reused non-default branch moving beyond a terminal PR. #4970 needs that remaining case too. |

What changed
resolveDefaultBranchNameand the status-pathdefaultBranchCachenow ask the remote for its default branch withgit ls-remote --symref <remote> HEAD, and fall back to the localrefs/remotes/<remote>/HEADsnapshot only when the remote cannot be reached.Why
refs/remotes/<remote>/HEADis written once, when the repository is cloned, and neitherfetchnorpullever moves it again — only an explicitgit remote set-head <remote> -adoes. A project that changes its default branch after you cloned leaves your clone pointing at the old one, permanently and silently. Nothing in the codebase asked the remote.That value is load-bearing.
readRemoteStatusinGitManager.tssuppresses merged/closed change requests only whenisDefaultBranchis true:When the snapshot is stale, the real default branch is classified as an ordinary topic branch, the guard never fires, and
findLatestPrForHeadContextfalls through toparsed[0]— the most recently updated change request sourced from that branch, in any state. A thread on a long-lived default branch such asdevthen binds to a change request that merged weeks earlier and re-settles after every completed turn.Reproduced on a GitLab project whose default branch is
devwhile the clone'sorigin/HEADstill pointed atorigin/main: a thread created ondevwas bound to a merge request last updated five weeks before the thread existed, and manual un-settles did not stick. Details in #4970.The same stale value also feeds
aheadOfDefaultCountand theisDefaultflag inlistBranches, so both get more accurate as a side effect.Cost
One extra git subprocess per repository per cache window, on paths that already do network work:
STATUS_UPSTREAM_REFRESH_ENVandSTATUS_UPSTREAM_REFRESH_TIMEOUT, the same non-interactive env and 5s timeout as the background status fetch, so it cannot prompt for credentials or hang.ls-remoteis plain git over the existing transport — no forge CLI, no API quota, and it works identically for GitHub, GitLab, Bitbucket, Azure DevOps and self-hosted remotes.Tests
Two integration tests in
GitVcsDriverCore.test.ts, both against real git repositories:prefers the remote default branch over a stale local origin HEAD— remote HEAD isdev, local snapshot saysmain, assertsisDefaultBranchis true ondev. Fails onmainbefore this change.falls back to the local origin HEAD when the remote is unreachable— origin URL points at a nonexistent path, asserts the local snapshot still resolves.vp fmt --check,vp lintandtsgo --noEmitare clean. The full server suite has 13 pre-existing failures oncebac35in my environment (11 inGitManager.test.tsPR-worktree tests, 1 inProviderRegistry.test.ts, 1 inGitVcsDriverCore.test.tsnewline-path test); the set is identical with and without this change.Notes
I read CONTRIBUTING — happy for this to sit or be closed. It is scoped to one behaviour and adds no surface area. The complementary fix discussed in #4970, making the terminal change-request fallback time-aware so a recycled feature branch cannot inherit its own historical MR, is deliberately not in here.
Note
Medium Risk
Touches core VCS status and PR-binding logic (
isDefaultBranch,aheadOfDefaultCount) with extra network subprocesses, but behavior is gated by caching and existing non-interactive fetch timeouts.Overview
Default branch detection now asks the remote for HEAD instead of trusting
refs/remotes/origin/HEAD, which Git sets at clone time and does not update when the forge moves the default branch.GitVcsDriverCoreadds aremoteDefaultBranchCachethat runsgit ls-remote --symref <remote> HEAD(non-interactive env, 5s timeout). Successful probes cache for 30 minutes; null/unreachable results and probe failures use the shorter 5-minute TTL so offline or broken remotes are retried without hammering every status read. That cache is not cleared on local mutations like fetch, since those cannot change the remote’s default HEAD.defaultBranchCacheandresolveDefaultBranchNameprefer the live remote answer and fall back tosymbolic-ref refs/remotes/<remote>/HEADonly when the probe yields nothing. NewparseDefaultBranchFromLsRemoteSymrefparses the symref line fromls-remoteoutput.Integration tests cover remote-wins over a stale local
origin/HEAD, unreachable-remote fallback, and failed-probe cooldown across fetch + clock advance.Reviewed by Cursor Bugbot for commit f4ff541. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Resolve the default branch from the remote via
git ls-remoteinstead of stale localorigin/HEADremoteDefaultBranchCachein GitVcsDriverCore.ts that runsgit ls-remote --symref <remote> HEADto fetch the current default branch, caching successes for 30 minutes and failures/nulls for 5 minutes.defaultBranchCacheandresolveDefaultBranchNameto prefer the live remote result and fall back togit symbolic-ref refs/remotes/origin/HEADonly when the remote is unreachable.origin/HEADis only used as a fallback.Macroscope summarized f4ff541.