Skip to content

fix(pr-validation): treat a cancelled approval as superseded - #912

Merged
bedatty merged 2 commits into
developfrom
fix/superseded-on-cancelled-approval
Oct 16, 2026
Merged

bedatty merged 2 commits into
developfrom
fix/superseded-on-cancelled-approval

Conversation

@bedatty

@bedatty bedatty commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Description

Found by exercising #906, not by reading the code.

Editing a pull request description fires edited, which queues a new run on the same commit. The concurrency group from #908 cancelled the previous pending approval — correct — but the superseded guard only compared head SHAs, and the head had not moved.

So the dead run fell through to unresolved, overwrote the live comment with "💥 Breaking change — decision not recorded" and failed its check, while the new run was sitting right there holding the real request. The comment told the reader the opposite of what was true.

Run 38833329346 approval cancelled, verdict ran anyway → clobbered the comment
Run 38833721558 same commit ab25957, waiting with the real request

A cancelled approval is now superseded on its own, checked before the head comparison because it is the broader signal: nothing cancels it except the concurrency group replacing the run or a human doing it by hand, and neither is a decision worth reporting. The head comparison stays for whatever slips past that.

Type of Change

  • feat: New workflow or new input/output/step in an existing workflow
  • fix: Bug fix in a workflow (incorrect behavior, broken step, wrong condition)
  • perf: Performance improvement (e.g. caching, parallelism, reduced steps)
  • refactor: Internal restructuring with no behavior change
  • docs: Documentation only (README, docs/, inline comments)
  • ci: Changes to self-CI (workflows under .github/workflows/ that run on this repo)
  • chore: Dependency bumps, config updates, maintenance
  • test: Adding or updating tests
  • BREAKING CHANGE: Callers must update their configuration after this PR

Breaking Changes

None. One more condition in an existing step; no input, output or job name changes.

Testing

  • YAML syntax validated locally
  • Triggered a real workflow run on a caller repository using @this-branch or the beta tag
  • Verified all existing inputs still work with default values
  • Confirmed no secrets or tokens are printed in logs
  • Checked that unrelated workflows are not affected

The failing case is reproduced on #906 and visible there.

Verdict logic exercised against a stubbed gh across seven paths: cancelled with the same head (the case that broke), cancelled with a different head, different head with the approval fine, approved, rejected, failed-without-a-recorded-rejection, and nothing-to-approve. Only the first three resolve to superseded; the rest are unchanged.

Test pins the new branch and its position ahead of the head comparison, verified by mutation — removing the guard fails it.

Caller repo / workflow run: https://github.com/LerianStudio/github-actions-shared-workflows/actions/runs/38833329346

Related Issues

Caught on #906. Editing the pull request description fires `edited`,
which queues a new run on the very same commit. The concurrency group
cancelled the previous pending approval, correctly — but the superseded
guard only compared head SHAs, and the head had not moved. So the dead
run fell through to "decision not recorded", overwrote the live
revision's comment and failed its check, while the new run sat waiting
with the real request.

A cancelled approval now counts as superseded on its own, and is checked
first because it is the broader signal: nothing cancels it except the
concurrency group replacing the run or a human doing it by hand, and
neither is a decision worth reporting. The head comparison stays for
whatever slips past that.

Test pins both the new branch and its order ahead of the comparison.
@bedatty
bedatty requested a review from a team as a code owner October 16, 2026 01:15
@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: LerianStudio/github-actions-shared-workflows/.coderabbit.yml
  • Review profile: ASSERTIVE
  • Plan: Essentials
  • Run ID: 08392bad-acf3-473f-a060-fba67cf06731
📥 Commits

Reviewing files that changed from the base of the PR and between 1b4ea5f and b134e61.

📒 Files selected for processing: 1
  • src/validate/breaking-change-guard/test-workflow.py

Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Walkthrough

The approval outcome step now handles cancellation before comparing the pull request head. It marks the run as superseded and exits without further pull-request actions. The test and documentation cover cancellations when the head remains unchanged.

Changes

Approval cancellation handling

Layer / File(s) Summary
Cancelled approval outcome
.github/workflows/breaking-change-approval.yml, src/validate/breaking-change-guard/test-workflow.py, docs/breaking-change-approval.md
The workflow marks a cancelled approval as superseded before comparing the live head SHA, then exits without further pull-request actions. The test checks this ordering. The documentation describes cancellation sources and same-head replacements.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to b134e

Cancelled approvals are handled before the head check, and no actionable merge-blocking risk remains.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 1 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the primary fix: treating cancelled approvals as superseded.
Description check Passed The description explains the problem, affected behavior, implementation, testing, and lack of breaking changes. The Related Issues section is empty, but the required issue reference is not critical be…
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.
Full details: Description check

Explanation

The description explains the problem, affected behavior, implementation, testing, and lack of breaking changes. The Related Issues section is empty, but the required issue reference is not critical because the description identifies the reproduced case and objective.

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@lerian-studio lerian-studio added size/S PR changes 50–199 lines documentation Improvements or additions to documentation workflow Changes to one or more reusable workflow files validate Changes to PR validation composite actions (src/validate/) labels Oct 16, 2026
@lerian-studio

lerian-studio commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

🔍 Lint Analysis

Check Files Scanned Status
YAML Lint 1 file(s) ✅ success
Action Lint 1 file(s) ✅ success
Pinned Actions 1 file(s) ✅ success
Markdown Link Check 1 file(s) ✅ success
Spelling Check 3 file(s) ✅ success
Shell Check 1 file(s) ✅ success
README Check 1 file(s) ✅ success
Composite Schema no changes ⏭️ skipped
Deployment Matrix no changes ⏭️ skipped

🔍 View full scan logs

@lerian-studio

lerian-studio commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

🛡️ CodeQL Analysis Results

Languages analyzed: actions

Found 1 issue(s): 1 Medium

Severity Rule File Message
🟡 Medium actions/untrusted-checkout/medium .github/workflows/breaking-change-approval.yml:260 Potential unsafe checkout of untrusted pull request on non-privileged workflow.

🔍 View full scan logs | 🛡️ Security tab

@lerian-studio

lerian-studio commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

🔍 PR Validation Summary

✅ PR Mergeable — no blocking failures

Check Status Blocking
Source Branch ✅ success yes
PR Title ✅ success yes
PR Description ✅ success yes
Breaking Change Guard ✅ success yes
Commit Signatures ✅ success yes
PR Size ✅ success no
Auto Labels ✅ success no
PR Metadata ✅ success no

🔍 View workflow run

@lerian-studio

This comment has been minimized.

@lerian-studio lerian-studio added the review-ready Required checks passed — CodeRabbit is cleared to review label Oct 16, 2026
@coderabbitai

This comment has been minimized.

coderabbitai[bot]

This comment was marked as resolved.

Addresses the CodeRabbit review on #912.

The cancellation test checked the branch and its ordering but not its
`exit 0`. Without that exit a cancelled run falls through, and a
recorded rejection downstream wins: the verdict comes back `rejected`
and the pull request is closed — because a concurrency cancellation
raced a reviewer. Closing a pull request by accident is the most
destructive thing this workflow does, so "trivial" undersells it.

Asserting the characters `exit 0` are present would prove nothing about
whether the branch stops, and would be the fifth text assertion in this
series. `run_verdict()` executes the step with `gh` stubbed instead, and
the test hands it a rejection to fall through onto. Removing the exit
makes it fail with 'superseded' != 'rejected' — which is how the report
was confirmed in the first place.

Also covers the whole state table by running it: approved, rejected,
superseded from a cancellation, superseded from a moved head,
unresolved, none, blocked. Those cases existed only in a scratch harness
before.
@lerian-studio

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@bedatty
bedatty merged commit c88c352 into develop Oct 16, 2026
48 checks passed
@github-actions
github-actions Bot deleted the fix/superseded-on-cancelled-approval branch October 16, 2026 12:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation review-ready Required checks passed — CodeRabbit is cleared to review size/S PR changes 50–199 lines validate Changes to PR validation composite actions (src/validate/) workflow Changes to one or more reusable workflow files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants