Skip to content

Show PR status for branch worktrees at a merged PR head#6607

Merged
brennanb2025 merged 2 commits into
mainfrom
brennanb2025/pr-sidebar-broad-refresh
Jun 28, 2026
Merged

Show PR status for branch worktrees at a merged PR head#6607
brennanb2025 merged 2 commits into
mainfrom
brennanb2025/pr-sidebar-broad-refresh

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

What changes

The left worktree sidebar now shows PR/review status for branch worktrees whenever it makes sense — in particular, a merged GitHub PR whose head commit equals the worktree's checked-out HEAD now shows a PR badge in the left sidebar, matching what the right Checks panel already shows.

What does not change: a worktree that has moved past a merged PR's head (checked out at a different commit) still shows branch-only — a historical merged PR is not resurrected. Detached-HEAD worktrees are out of scope here. Non-GitHub providers (GitLab/Bitbucket/Azure DevOps/Gitea) are unaffected.

Root cause

The merged-PR visibility check hid a merged PR unless the current HEAD equalled the PR head, but it read the current HEAD by running git rev-parse HEAD against the registered (main) repo path, not the worktree being inspected. For any secondary worktree the main repo is almost always on a different commit, so the merged PR was hidden even when the worktree was sitting exactly on it. The right Checks panel avoided this because it passes a fallback PR number that keeps the PR alive; the left sidebar passed none.

Approach

  • Thread the worktree's checked-out HEAD oid (currentHeadOid) end-to-end into the GitHub branch lookup (renderer → IPC/runtime RPC → forge provider → getPRForBranchOutcome). The merged-at-head decision now uses the worktree's own HEAD. When no oid is supplied, behavior is exactly as before (no new git call). The oid is supplied from worktree.head, which the renderer already has — so no new git rev-parse per card.
  • Head-gate fallback PR numbers in both the sidebar card and the refresh coordinator so a worktree that has moved past a merged PR head withholds the fallback and does not re-fetch the stale merged PR back into view.
  • Make a head-matched cached merged PR authoritative for the card's display so the badge actually surfaces (preferring fresher hosted-review data for the same PR).
  • Preserve the last known review on a transient lookup failure (timeout/rate-limit/gh error) instead of caching a definitive "no review" miss for the full TTL — this was the cause of the badge disappearing then reappearing.

currentHeadOid lives only on the GitHub lookup path; other providers ignore it. SSH/runtime supply the oid from the already-transported worktree.head, so there is no new server-side git exec.

Design review

Ran an automated design-review loop (5 iterations to convergence). It tightened the cache-interaction story: the warm-cache badge surfaces through the prCache display path (not the hint-suppressed hosted-review entry), and the head-gate must hold at every fallback-number source (sidebar refresh, hover-reveal, and the coordinator candidate) or a moved-past merged PR can be re-seeded. Both reviewers returned clean on the final pass.

Implementation & deviations

Implemented as designed. The end-to-end currentHeadOid threading, the head-gating at all three fallback sources, and the warm-case display preference are all in place. One belt-and-suspenders backend guard was added: when an explicit head oid differs from a merged PR's head, neither the confirmed-merged-branch path nor acceptMergedFallbackPR overrides the hide — this also brings the right Checks panel into alignment (it no longer keeps a moved-past merged PR). No deviations from the design intent.

Completeness verification

A read-only verification pass confirmed all 13 design requirements are implemented (Change 1 threading: 9/9; Change 2 head-gate + warm display: 4/4), with file:line evidence, and confirmed no eslint-disable max-lines was added.

Headline behavior status

Implemented and validated in the running app. The merged-at-head PR badge appears in the left sidebar in production code paths (not fixtures/scaffolding).

Performance

Audited against the perf checklist. No findings. Touched hot paths: the per-visible-card hosted-review refresh effect and the main-process branch lookup. No new git rev-parse on the common (oid-supplied) path; gh-call parity with the right Checks panel (the exact PR lookup only runs when branch lookup misses, which does not happen in the merged-at-head common case); buildPRRefreshCandidate head-gate is a pure local comparison that only ever withholds a fallback; no new store-subscription churn, no unbounded scans, no new logging; #6203 burst/concurrency bounds untouched.

Code review

review-code two-reviewer loop completed in one round with both reviewers clean (no actionable findings).

Validation

  • Electron product validation: built and ran this branch, created a demo-project worktree checked out at merged PR Lint long files (and add TS skills) #23's head (unlinked, cold cache), with the main repo on a different commit. The left sidebar showed PR: Merged with the merged-PR badge; the hover-card showed PR #23 · State: Merged · Checks: Failing; the right Checks panel showed #23 MERGED — full parity. A second worktree advanced past the merged head correctly showed branch-only. Console: 0 errors; no git/gh/hosted-review stack traces.
  • Static + tests: pnpm run typecheck clean; pnpm run lint clean (one pre-existing unrelated useGitStatusPolling.ts warning); broad targeted vitest sweep (renderer store/sidebar/right-sidebar + main github/source-control/ipc/runtime) — all green (4000+ tests). Existing call-shape assertions updated for the new currentHeadOid field; added focused tests for the explicit-oid hide/keep, the upstream-error throw, the transient-error cache preservation, and the coordinator head-gate.

Assumptions / residual risk

  • SSH/runtime and non-GitHub providers were not exercised live; both are low-risk by construction (oid transported from the renderer, GitHub-scoped field) and covered by unit tests.
  • The coordinator prCache path remains main-repo-scoped by design (it relies on the head-gate to avoid stale merged PRs); the cold-cache badge is served by the hosted-review path.

Made with Orca 🐋

The left worktree sidebar showed only the branch (no PR) for a worktree
checked out at a merged PR's head commit, while the right Checks panel
showed the PR. The merged-PR visibility check compared the PR head against
the main repo's HEAD instead of the worktree's HEAD, so it hid the PR for
every secondary worktree.

Thread the worktree's checked-out HEAD oid into the GitHub branch lookup so
the merged-at-head decision uses the worktree's own HEAD. Head-gate fallback
PR numbers so a worktree that has moved past a merged PR head does not
resurrect it. Also preserve the last known review on a transient lookup
failure instead of caching a definitive 'no review' miss.

currentHeadOid is GitHub-only; GitLab/Bitbucket/Azure/Gitea are unaffected.
No new git/gh calls in the common path and no new logging.

Co-authored-by: Orca <help@stably.ai>
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This change threads currentHeadOid through the hosted review lookup path from renderer calls into shared IPC/RPC contracts, runtime, source-control logic, and GitHub branch lookup. The GitHub client now uses the supplied head OID when deciding whether to hide merged implicit PRs and when preserving merged fallback results. Renderer store logic and components pass the current worktree head into refresh requests, and the cache path now preserves prior review data on lookup failure while avoiding stale merged-review reuse.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it does not follow the required template and omits screenshots, an explicit AI review report, and a security audit section. Add the missing template sections, include screenshots or note no visual change, and explicitly summarize the AI review and security audit, including cross-platform checks.
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly describes the main change: showing PR status for branch worktrees at a merged PR head.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/renderer/src/store/slices/github.ts (1)

1031-1046: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Head-gate the hosted-review fallback too.

The new guard only checks cachedPR, but hostedReviewFallbackPRNumber can still return a stale merged PR number when prCache is empty. That lets a moved-past worktree re-enqueue the historical PR via the hosted-review cache. Return the hosted review metadata here and suppress GitHub merged fallbacks when headSha !== worktree.head.

Suggested direction
-  const hostedReviewFallbackPRNumber = githubHostedReviewFallbackPRNumber(
+  const hostedReviewFallback = githubHostedReviewFallbackReview(
     state,
     repoPath ?? repo.path,
     repo.id,
@@
-  const fallbackPRNumber =
-    worktree.linkedPR == null && !cachedMergedPRMovedPastHead
-      ? (cachedFallbackPRNumber ?? hostedReviewFallbackPRNumber)
+  const hostedReviewMergedPRMovedPastHead =
+    worktree.linkedPR == null &&
+    hostedReviewFallback?.provider === 'github' &&
+    hostedReviewFallback.state === 'merged' &&
+    hostedReviewFallback.headSha !== worktree.head
+  const fallbackPRNumber =
+    worktree.linkedPR == null &&
+    !cachedMergedPRMovedPastHead &&
+    !hostedReviewMergedPRMovedPastHead
+      ? (cachedFallbackPRNumber ?? hostedReviewFallback?.number ?? null)
       : null

Also applies to: 1086-1105

src/renderer/src/components/right-sidebar/ChecksPanel.tsx (1)

1189-1195: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Thread currentHeadOid through every hosted-review refresh path.

The automatic effect and entry refresh now send activeWorktree?.head, but the manual refresh, mutation refresh, linked-PR refresh, and post-create refreshHostedReviewCard calls still omit it. Those secondary-worktree paths can fall back to the main repo HEAD and reproduce the merged-PR visibility bug. Add currentHeadOid: activeWorktree?.head ?? null to the remaining refreshHostedReviewCard option objects and include it in the relevant callback dependencies.

Also applies to: 1871-1881, 1925-1935, 2085-2091, 2183-2193, 2212-2222, 2863-2872, 3139-3160


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ed8a31eb-544d-403b-940c-aaff05be4607

📥 Commits

Reviewing files that changed from the base of the PR and between c50391d and b9e32a3.

📒 Files selected for processing (19)
  • src/main/github/client.ts
  • src/main/ipc/hosted-review.ts
  • src/main/runtime/orca-runtime.ts
  • src/main/runtime/rpc/methods/hosted-review.test.ts
  • src/main/runtime/rpc/methods/hosted-review.ts
  • src/main/source-control/forge-provider.test.ts
  • src/main/source-control/forge-provider.ts
  • src/main/source-control/hosted-review.test.ts
  • src/main/source-control/hosted-review.ts
  • src/renderer/src/components/right-sidebar/ChecksPanel.tsx
  • src/renderer/src/components/sidebar/WorktreeCard.hosted-review-refresh.test.tsx
  • src/renderer/src/components/sidebar/WorktreeCard.pr-display.test.tsx
  • src/renderer/src/components/sidebar/WorktreeCard.tsx
  • src/renderer/src/store/slices/github.test.ts
  • src/renderer/src/store/slices/github.ts
  • src/renderer/src/store/slices/hosted-review-cache-race.test.ts
  • src/renderer/src/store/slices/hosted-review.test.ts
  • src/renderer/src/store/slices/hosted-review.ts
  • src/shared/hosted-review.ts

Comment thread src/renderer/src/store/slices/hosted-review.ts
The branch-scoped hosted-review cache could return (or, on a failed
refresh, preserve) a merged GitHub PR for a head the worktree has moved
off of, within the cache TTL. Skip cache reuse and error-preservation
when a cached merged GitHub review's headSha no longer matches the
requested worktree HEAD, forcing a fresh head-aware lookup.

Co-authored-by: Orca <help@stably.ai>

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

🧹 Nitpick comments (2)
src/renderer/src/store/slices/hosted-review.test.ts (2)

534-547: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the refetch forwards the updated currentHeadOid.

This currently proves only that a second lookup happens. Because the mock is value-only, the test still passes if the refetch sends the stale head or null, which is the contract this PR is threading through. Add a toHaveBeenNthCalledWith(...) assertion for the first call using 'aaaaaaa' and the second using 'bbbbbbb'.


550-595: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Lock down the cache state after the failed forced refresh.

resolves.toBeNull() verifies the failed fetch does not immediately fall back to the stale merged review, but it does not catch leaving that stale entry reusable in hostedReviewCache. A follow-up non-force read or direct cache assertion would cover the regression described in the PR objective.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1194cb55-8dee-48e5-81c8-6ff625735141

📥 Commits

Reviewing files that changed from the base of the PR and between b9e32a3 and 43c3ae6.

📒 Files selected for processing (2)
  • src/renderer/src/store/slices/hosted-review.test.ts
  • src/renderer/src/store/slices/hosted-review.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/src/store/slices/hosted-review.ts

@brennanb2025 brennanb2025 merged commit a71b865 into main Jun 28, 2026
1 check passed
@brennanb2025 brennanb2025 deleted the brennanb2025/pr-sidebar-broad-refresh branch June 28, 2026 18:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant