GitHub PR Approve Helper v1.3.0: approve a whole stack, fix vanishing button and stale state - #9
Conversation
On the files page the quick-approve button appears, then vanishes a few seconds later once the conversation-page fetch resolves: detectPrStateInDoc took any state badge in the document, so a "Merged"/"Closed" badge on a timeline cross-reference or linked issue was read as the PR's own state. Now the first badge that reads as a PR state decides, and an open/draft header ends the scan. The state log also names the badge it matched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MM2QDa5SMgF9CRr94AZzyW
The plain click still approves only the current PR. On a PR that is part of a stack (the N/M badge next to the state label), the right-click menu gains "Approve whole stack", which approves every PR bottom to top with the default comment, skipping merged/closed/own/already-approved ones and reporting the tally on the button. Stack members are read from the page's embedded JSON payload (graphql shape: stack.entries[].pullRequest.number), falling back to the stack map popover opened through the badge. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MM2QDa5SMgF9CRr94AZzyW
Jumping between PRs through the stack popover is a soft navigation: the url already names the next PR while the previous PR's sidebar is still in the dom, so the live detection read the old "approved" and cached it on the new PR. The fetched conversation page is now the only cached source; the live page is an uncached hint, used only once the tab title names the current PR. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MM2QDa5SMgF9CRr94AZzyW
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MM2QDa5SMgF9CRr94AZzyW
One approvePr primitive serves the plain click and the stack run, one cache-aware loadPrState serves the button and the stack run, and a stack member's conversation page is fetched once for both its state and its csrf token. The stack payload walker now only accepts the documented stack.entries shape, the popover is found from its pr links, and the badge lookup does the cheap text test first. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MM2QDa5SMgF9CRr94AZzyW
There was a problem hiding this comment.
🟡 Changes recommended
On conversation pages the quick-approve button can briefly render for merged/closed/approved PRs due to async-only state caching in getPrDisplayState.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR updates the GitHub PR Approve Helper userscript to support approving an entire LinearB/GitHub PR stack, and fixes state-detection/caching issues that could incorrectly hide the quick-approve button or show an “Already approved” state after soft navigation.
Changes:
- Add a “Approve whole stack (N PRs)” action to the quick-approve context menu and implement bottom-to-top stack approval with skipping logic.
- Tighten PR state detection to rely on the header badge (avoiding false “Merged/Closed” from timeline cross-references) and rework state loading/caching.
- Update README metadata (last-updated date) and bump userscript version to 1.3.0.
File summaries
| File | Description |
|---|---|
| README.md | Updates the “Updated” date for the GitHub PR Approve Helper entry. |
| github-pr-approve-helper.user.js | Implements stack approval + refactors PR state/token discovery and caching to fix UI/state issues. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
On the conversation page the state was loaded through the async path even though the document was already there, so the quick-approve button could render for one frame on a merged or already-approved PR before the scan re-ran. Detect from the live document synchronously there. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MM2QDa5SMgF9CRr94AZzyW
There was a problem hiding this comment.
🟡 Changes recommended
Verified edge cases can (1) throw during JSON payload scanning when script.textContent is null and (2) incorrectly show “🎉 Approved” when a single-PR action was actually skipped.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
github-pr-approve-helper.user.js:326
jsonPayloadsassumesscript.textContentis always a string. In the DOM API it can benull(e.g., an empty script node), which would throw at.includes(...)and break CSRF/stack parsing.
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
jsonPayloads treats a null textContent as empty instead of throwing. When every pr of a run was skipped (merged, closed, own or approved meanwhile) the button now says so rather than showing the success label. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MM2QDa5SMgF9CRr94AZzyW
There was a problem hiding this comment.
🟡 Changes recommended
The stack-run path can treat the current PR as open while its authoritative conversation-page state is still loading, which can bypass the intended skip logic and cause incorrect approvals or run failures.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
| const isCurrent = pr.number === parsePrPath()?.number; | ||
| const doc = isCurrent ? null : await fetchPrDoc(pr); | ||
| const state = isCurrent ? getPrDisplayState(pr) : await loadPrState(pr, doc); | ||
| if (state !== 'open') { |
Summary
2/3next to the state label), the right-click menu of the quick-approve button gains "🥞 Approve whole stack (N PRs)". It approves bottom to top with the default comment, skips merged/closed/own/already-approved PRs, and shows the tally on the button. The plain click still approves only the current PR.approvePrprimitive for both paths, one cache-awareloadPrState, one page fetch per stack member for both its state and its csrf token.Stack members are read from the page's embedded JSON payload (GraphQL shape
stack.entries[].pullRequest.number), falling back to the stack map popover opened through the badge.Test plan
Verified in headless Chromium against mocked GitHub pages:
Manually tested by @MishaKav on a linear-b stack (#254-#256).
🤖 Generated with Claude Code
https://claude.ai/code/session_01MM2QDa5SMgF9CRr94AZzyW
Generated by Claude Code