The panel comment is deliberately preserved after merge (src/queue/processors.ts ~8841-8845, #preserve-review-on-close) and always renders the re-run checkbox (~11565). GitHub task-list checkboxes stay interactive on closed PRs, so a merged PR carries a live re-run button forever.
maybeProcessPrPanelRetrigger (~12898-13075) validates bot_author, missing_repo_pr_or_installation, cached_pr_missing, and authorization — but never checks pr.state. A post-merge click therefore: records github_app.pr_panel_retriggered outcome completed (~13004, before any outcome is known), force-refreshes the dead PR, and runs deep into the pipeline (repo/issue/bounty loads, gate evaluation, slop, live CI reads). prReadyForReview returns true unconditionally for a non-open PR (~3855-3861), so it can never defer.
It is finally killed by the freshness guard (~10033-10039 → classifyPullRequestFreshness "stale/closed", src/github/pr-freshness.ts ~77-79), which writes github_app.pr_review_stale denied and returns — then the handler records product usage completed. Net: contradictory telemetry, real GitHub/DB spend, no user-visible feedback, checkbox left checked.
The sibling entry point guards this twice (reReviewStoredPullRequest ~3603 stored state, ~3633-3644 live state) with a comment stating the invariant verbatim — the retrigger handler implements neither.
Second harm on gate-enabled repos: the post-merge click reaches the ~8840 branch and createOrUpdateSkippedGateCheckRun (src/github/app.ts ~806-834, updateExisting:"any" semantics at ~1053-1080) PATCHes the merged PR's completed green gate run to skipped — falsifying the historical verdict on a fully-evaluated merged head.
Fix
Add a pr.state !== "open" guard right after the cached_pr_missing check (~12954) → recordPrPanelRetriggerSkip(..., "pr_not_open"), mirroring ~3638-3644. Separately, never let createOrUpdateSkippedGateCheckRun demote an already-completed gate run.
Refs #9000.
The panel comment is deliberately preserved after merge (
src/queue/processors.ts~8841-8845, #preserve-review-on-close) and always renders the re-run checkbox (~11565). GitHub task-list checkboxes stay interactive on closed PRs, so a merged PR carries a live re-run button forever.maybeProcessPrPanelRetrigger(~12898-13075) validatesbot_author,missing_repo_pr_or_installation,cached_pr_missing, and authorization — but never checkspr.state. A post-merge click therefore: recordsgithub_app.pr_panel_retriggeredoutcome completed (~13004, before any outcome is known), force-refreshes the dead PR, and runs deep into the pipeline (repo/issue/bounty loads, gate evaluation, slop, live CI reads).prReadyForReviewreturnstrueunconditionally for a non-open PR (~3855-3861), so it can never defer.It is finally killed by the freshness guard (~10033-10039 →
classifyPullRequestFreshness"stale/closed",src/github/pr-freshness.ts~77-79), which writesgithub_app.pr_review_staledenied and returns — then the handler records product usage completed. Net: contradictory telemetry, real GitHub/DB spend, no user-visible feedback, checkbox left checked.The sibling entry point guards this twice (
reReviewStoredPullRequest~3603 stored state, ~3633-3644 live state) with a comment stating the invariant verbatim — the retrigger handler implements neither.Second harm on gate-enabled repos: the post-merge click reaches the ~8840 branch and
createOrUpdateSkippedGateCheckRun(src/github/app.ts~806-834,updateExisting:"any"semantics at ~1053-1080) PATCHes the merged PR's completed green gate run toskipped— falsifying the historical verdict on a fully-evaluated merged head.Fix
Add a
pr.state !== "open"guard right after thecached_pr_missingcheck (~12954) →recordPrPanelRetriggerSkip(..., "pr_not_open"), mirroring ~3638-3644. Separately, never letcreateOrUpdateSkippedGateCheckRundemote an already-completed gate run.Refs #9000.