Skip to content

submit-pull-request-review: add supersede-older-reviews option to dismiss stale bot reviews on re-review #27655

@PureWeen

Description

@PureWeen

Problem

When an agent workflow posts a REQUEST_CHANGES review via submit-pull-request-review, the blocking review persists even after the author fixes all findings and triggers a re-review. The new review run cannot dismiss the stale blocking review because:

  1. gh-aw enforces pull-requests: readpull-requests: write is rejected by the compiler (correctly, for security)
  2. There is no dismiss-pull-request-review safe output type
  3. The hide-older-comments: true option on add-comment has no equivalent for reviews

Net result: A stale CHANGES_REQUESTED review from github-actions[bot] blocks the PR indefinitely until a human manually dismisses it.

Real-World Impact

  • PureWeen/PolyPilot#619: A 3-model adversarial code review posted REQUEST_CHANGES on commit 1. The author fixed all 4 findings in commits 2-6. Re-running /review posted a new review but the old blocking review persisted, preventing merge.
  • dotnet/maui#35027: The MAUI repo adopted allowed-events: [COMMENT] as a workaround after discovering the same issue.
  • gh-aw#25869: The design-decision-gate workflow independently removed submit-pull-request-review entirely to avoid stale blocking reviews — same workaround pattern.
  • tailspin-toys-workshop: The gh-aw workshop teaches allowed-events: [COMMENT, REQUEST_CHANGES] as the recommended pattern, which would hit this problem if students use /review re-runs on their workflows.

Current Workaround

Use allowed-events: [COMMENT] to prevent the agent from posting blocking reviews. The review body still communicates severity through findings, but this loses:

  • GitHub-native "Changes requested" badge and merge-blocking semantics
  • PR list discoverability (no blocking indicator at a glance)
  • Branch protection integration (bot reviews cannot gate merge)

Proposed Solution

Add a supersede-older-reviews: true option to submit-pull-request-review, analogous to hide-older-comments: true on add-comment:

safe-outputs:
  submit-pull-request-review:
    max: 1
    allowed-events: [COMMENT, REQUEST_CHANGES]
    supersede-older-reviews: true  # dismiss prior bot reviews from same workflow

Behavior

When posting a new review, auto-dismiss previous REQUEST_CHANGES reviews from the same workflow (identified by GITHUB_WORKFLOW / workflow-id marker) on the same PR. The dismissal message would be "Superseded by updated review from same workflow."

Why not a standalone dismiss-pull-request-review safe output?

A standalone dismiss primitive has a wider attack surface — an agent could be prompt-injected into dismissing legitimate human reviews. supersede-older-reviews is structurally safer because:

  • Scoped to the same workflow's own prior reviews (never human reviews)
  • Atomic with posting a new review (cannot dismiss without replacing)
  • Matches the existing hide-older-comments pattern exactly
  • No new permissions exposure — the pull-requests: write needed for dismissal stays inside platform infrastructure

Security Analysis (4-model adversarial consensus)

This proposal was reviewed by 4 independent AI models with adversarial consensus. All 4 unanimously agreed the problem is real. However, on the proposed solution, 2/3 disagreed — raising important concerns that should inform the design:

Threat Risk
Prompt injection → dismiss human reviews Impossible — scoped to same-workflow bot reviews only
Prompt injection → dismiss to unblock merge Cannot dismiss without simultaneously posting a replacement review
Branch protection bypass Marginal — the new review replaces the old one, so net review state is fresh, not absent
Scope creep Cannot target other bots/humans — scoped by workflow identity

⚠️ Counter-Arguments (from adversarial review)

We ran a 3-model adversarial review of this issue itself. Two of three reviewers disagreed with the proposed supersede-older-reviews design, raising valid concerns:

Atomicity is false

The hide-older-comments analogy breaks down because dismiss + post is two separate GitHub API calls, not a transaction. If dismiss succeeds but the new review POST fails (network error, rate limit, model timeout), the PR is left unblocked with no review — a security regression.

Auto-merge race condition

If a repo has auto-merge enabled: dismiss fires → PR satisfies all merge conditions → GitHub auto-merges → new review arrives after merge. The PR merges without the replacement review ever being evaluated.

Stale REQUEST_CHANGES as a security ratchet

A stale blocking review requires human judgment to dismiss. This is arguably a security feature, not a bug — it prevents a prompt-injected "clean" re-review from automatically unblocking a PR that had legitimate findings. The human dismissal step is a trust boundary.

"Same workflow" identity is underspecified

How does the platform determine which previous reviews belong to "this workflow"? All gh-aw workflows share github-actions[bot] as the actor. Workflow file path, run ID lineage, or a platform metadata store? Each has different security properties.

Alternative Approaches

Given these concerns, the gh-aw team may prefer one of these alternatives:

Option A: Codify [COMMENT] as the recommended pattern (lowest risk)

Update the docs to explicitly recommend allowed-events: [COMMENT] for review workflows. This is already what every team in this issue converged on. Bot reviews inform; humans control merge-blocking.

Option B: Explicit dismiss-pull-request-review safe output (higher visibility)

A separate safe output (not bundled as a side effect of posting) with:

  • scope: same-workflow — only reviews posted by same workflow identity
  • require-replacement: true — must be paired with a new review in the same run
  • max: 1 — rate limited
    This makes the permission grant visible in the workflow definition and auditable.

Option C: create-check-run for bot blocking

Status checks (required_status_checks) are purpose-built for bot blocking signals — independent of the human review flow, easy to re-run and replace, no dismiss semantics needed.

Precedent

add-comment already supports hide-older-comments: true with similar lifecycle semantics. However, as noted in the counter-arguments, hiding a comment is cosmetic (content still accessible), while dismissing a REQUEST_CHANGES review is a branch protection state change — categorically different operations.

The current submit-pull-request-review docs (reference) confirm no lifecycle management option exists today. The only options are: max, target, target-repo, allowed-repos, allowed-events, footer.

Metadata

Metadata

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions