Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default 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 higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f764477. Configure here.
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This targeted fix changes production status and thread-settlement behavior by comparing terminal PR head OIDs with local or remote branch tips, alongside additive GitHub metadata plumbing. It also adds line-level static-analysis suppressions in the test file, so human review is warranted. You can add or adjust custom eligibility rules. Learn more. |
|
Both review findings were real. Fixed in 2afe53b. Cursor Bugbot — tip check matches sibling branch refs. Correct, with one refinement. So a sibling can never pin a badge on a live branch. The other half is the real one: the child only surfaces once the branch itself is gone — exactly when the merged badge is meant to be kept — and standing in for the deleted branch dropped it. Every row is now matched back against the ref it has to be, following the exact- Macroscope — stale badge when the local branch advances without pushing. The underlying observation is right, but dropping the Head commits are also compared case-insensitively now. Two tests added, both verified to fail against the previous commit:
248 tests pass across On the flagged suppressions — the only ones added are |
|
Both blocking findings are clear on 2afe53b — Cursor Bugbot pass (was 1 issue), Macroscope Correctness pass, and the approvability verdict no longer lists a blocking correctness issue. On the remaining note about line-level static-analysis suppressions in the test file: I checked whether they can be dropped, and they can't. Removing the four this PR adds fails The fake That leaves the verdict resting on "human review is warranted" for a change to status and settlement behavior, which seems right — flagging it for a maintainer rather than something further I can address in code. |
|
Note: GPT-6 on behalf of shivam (@shivamhwp). The GitManager filter does not clear an already saved stale badge on a shared checkout. If a thread acquires the old merged Please handle this downstream case too. Discovery needs enough information to distinguish a terminal match rejected because the branch moved from the legitimate historical-reference fallback. Preserve historical threads and explicit links. Include a new thread created after the old PR merged, initially on that PR's head, then advanced by its first turn. The rebase must retain the current GitHub |
594f45b to
34a3f5b
Compare
|
Thanks — you're right, and the downstream case is the one that actually bites. Rebased onto main and handled in 34a3f5b. The gap. Distinguishing the two. The lookup now keeps what it rejected instead of discarding it, and a new It reads the same cache entry the badge lookup reads, so there's no extra git or host work, and it's additive to the Preserved, as asked:
Your test case. Rebase. Kept main's Non-GitHub providers still have no 800 tests pass across |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (7)
🚧 Files skipped from review as they are similar to previous changes (1)
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review. 📝 WalkthroughWalkthroughThe change propagates pull request head commit OIDs through GitHub integrations, detects superseded terminal pull requests after branch movement, and prevents stale thread references from being restored. ChangesSuperseded pull request tracking
Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Bug fix Sequence Diagram(s)sequenceDiagram
participant ThreadPullRequestReactor
participant GitManager
participant GitRefs
ThreadPullRequestReactor->>GitManager: check branchSupersededPullRequest
GitManager->>GitRefs: read branch tip OID
GitManager-->>ThreadPullRequestReactor: return superseded status
ThreadPullRequestReactor-->>ThreadPullRequestReactor: restore or clear branch pull request
Suggested reviewers: Merge Risk: ⚪ Minimal · up to The change adds optional head-commit tracking to prevent stale terminal pull-request references from being restored. No actionable merge-blocking risk remains in the reviewed change. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
e21d327 to
2e3c820
Compare
|
Two follow-ups from the CodeRabbit pass, both fair. Description/diff mismatch. Correct — the description still described only the original lookup filter and never mentioned the discovery work added in the rebase. Rewritten to match what the branch actually does now: the lookup half, the Docstring coverage. The 0% reading looks like the heuristic not recognising No code behaviour changed in either follow-up. |
A long-lived branch keeps matching a merged pull request by name forever. Release `develop` into `main` and every later thread on `develop` is stamped with that same historical number, and settles against its merged state. `lookupStatusPr` already suppresses terminal matches, but only on the default branch, so an integration branch never qualifies. Compare the branch against the commit the change request was opened from instead: a branch still sitting on that commit is described by it, and a branch that has moved on was reused for later work. Commits rather than dates, so squash and rebase merges keep their badge — the recorded head commit is the branch's own, not the base's. `headRefOid` is optional, and the check is skipped whenever the answer is not knowable: a forge that does not report the head commit, a deleted branch with no ref left to compare, or a failed git call. Every one of those keeps today's behaviour rather than dropping a badge. Only GitHub populates it here; the other forges keep their current behaviour until they carry the field too. Refs pingdotgg#4970 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two problems with the first pass, both from review. `for-each-ref` matches a pattern literally *or* up to a slash, so `refs/heads/feature/foo` also reports `refs/heads/feature/foo/child`. Git forbids holding both at once, so the child only surfaces once the branch itself is gone — exactly when the merged badge is meant to be kept — and standing in for the deleted branch dropped it instead. Every row is now matched back against the ref it has to be, the way `branchPullRequest` already reads its own branch ref. Collecting every ref also meant a branch committed to but not yet pushed kept its badge, because the remote-tracking ref still sat on the old head and any match was enough. Read one tip instead, preferring the local ref: that is where a thread's work lands, so a branch with unpushed commits has still moved on from a merged change request. Head commits are compared case-insensitively. Refs pingdotgg#4970 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The lookup filter alone does not clear a badge that is already saved. A thread created after the release merged, while `develop` still sat on that change request's head, acquires the reference before its first turn moves the branch. The later lookup correctly reports nothing, but discovery reads that as "not found" and restores the saved reference because its summary is merged — putting back the very badge the lookup rejected. The fallback runs on the reused branch, not only after a return to the default branch. Discovery needs to tell the two apart, so the lookup now keeps what it rejected and `branchSupersededPullRequest` answers whether this branch outgrew a given change request. The retention fallback consults it and stands down only for that reference; numbers repeat across repositories, so the URLs have to agree. A saved reference the branch did not outgrow is still restored, and explicit links are untouched. Refs pingdotgg#4970 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The explanation sat only on the service interface, leaving the implementation undocumented. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`ChangeRequest.headRefOid` is a `Schema.optional` field, so its type admits an explicit `undefined`. `PullRequestInfo` declared it as `string | null`, which `exactOptionalPropertyTypes` rejects once a mapped change request is spread into it — as the Forgejo head-matching test now does. Widen it the way the neighbouring `PullRequestHeadRemoteInfo` fields already are. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2e3c820 to
3f3d8b7
Compare
|
Rebased onto current main ( Main hadn't picked up a text conflict, but a merge check against it failed typecheck. The Forgejo head-matching test added in #11436 spreads a mapped No behaviour change. On the rebased branch: full-repo typecheck and lint clean, 869 tests pass across |

Refs #4970.
A long-lived branch keeps matching a merged pull request by name forever. Release
developintomainand every later thread ondevelopis stamped with that same historical number, and settles against its merged state.There are two halves to it: the lookup that finds the stale match, and the discovery pass that puts it back after the lookup stops returning it.
1. The lookup
lookupStatusPralready suppresses terminal matches, but only on the default branch:An integration branch never qualifies, so it keeps its merged release PR indefinitely.
The branch is now compared against the commit the change request was opened from. A branch still sitting on that commit is described by it; a branch that has moved on was reused for later work.
ChangeRequestgains an optionalheadRefOid, from one extra field on thegh pr list --jsoncall that already runs — no extra request.Why commits and not dates. Comparing the branch tip's date against
updatedAtis simpler but breaks squash and rebase merges, and would have brokenstatus finds a merged PR after its remote branch was deleted, which backdates the PR while committing "now". The recorded head commit is the head branch's own, so it survives any merge strategy.Which ref. The local ref wins where there is one — that is where a thread's work lands, so unpushed commits still count as moving on.
for-each-refmatches a pattern literally or up to a slash, sorefs/heads/feature/fooalso reportsrefs/heads/feature/foo/child; every row is matched back against the ref it has to be, the waybranchPullRequestalready reads its own branch ref.2. The discovery pass
Filtering the lookup is not enough on its own.
ThreadPullRequestReactorrestores a thread's savedbranchPullRequestwhenever detection returnsnulland the saved reference is merged or closed. A thread created after the release merged — whiledevelopstill sat on that change request's head — acquires the reference before its first turn moves the branch. The lookup then correctly reports nothing, discovery reads that as "not found", and restores the very reference the lookup rejected. That fallback runs on the reused branch, not only after a return to the default branch.So
nullhad to stop being ambiguous. The lookup now keeps what it rejected, andbranchSupersededPullRequest(cwd, branch, pullRequest)answers whether this branch outgrew that specific change request. The retention fallback consults it and stands down only for that reference; numbers repeat across repositories, so the URLs have to agree.It reads the entry the badge lookup already cached, so it costs no extra git or host work, and it is additive to the
GitManagerinterface — existing callers and mocks are untouched.Preserved on purpose: a saved reference the branch did not outgrow is still restored, explicit
linkedPullRequestis untouched, and worktree threads skip the fallback exactly as before.Not knowable means "keep it"
The comparison returns
false, preserving today's behaviour, when the forge reports no head commit (every non-GitHub provider today), when no ref resolves — the deleted-branch case from #6216 — or when the git call fails. Losing it can never drop a badge that is otherwise correct.#7394 covers the separate stale-
origin/HEADcase.Tests
Against real git repositories with a fake
gh, each verified to fail without its fix:status drops a merged PR once its long-lived branch moves past itstatus drops a merged PR once its branch is committed to without pushingstatus keeps a merged PR while its branch still sits on the merged commit— the squash-merge casebranch PR lookup ignores a nested child ref once the branch itself is gonebranch lookup reports the terminal PR a reused branch has outgrownclears a saved reference the branch outgrew and keeps the rest of its history— a thread created after the old PR merged, on that PR's head, then advanced; alongside one holding a different historical reference and one with an explicit link800 tests pass across
src/git/,src/sourceControl/andsrc/orchestration/. Full-repo typecheck and lint clean. Rebased on main, keeping itsisDraft/closedAtfields and its 60sPR_LOOKUP_CACHE_TTL.No UI change — the badge stops appearing where it was wrong.
🤖 Generated with Claude Code
Summary by CodeRabbit