Skip to content

fix: prevent bot-triggered PR review loops#209

Open
derekmisler wants to merge 2 commits into
docker:mainfrom
derekmisler:fix/bot-review-loop
Open

fix: prevent bot-triggered PR review loops#209
derekmisler wants to merge 2 commits into
docker:mainfrom
derekmisler:fix/bot-review-loop

Conversation

@derekmisler
Copy link
Copy Markdown
Contributor

Problem

When docker-agent posts a review comment on a PR it triggers a second PR review run, creating an infinite loop. The root causes are:

  1. Identity fragility — every guard hard-coded the bare literal 'docker-agent' and/or sender.type != 'Bot'. PAT-based bots have user.type == 'User', so those checks silently missed them (documented in AGENTS.md as a known identity gap).
  2. Missing actor checks — several branches had no bot-skip guard at all (self-review-pr.yml issue_comment branch, review-pr.yml issue_comment branch).
  3. Asymmetric artifact-path guard — the reply-to-feedback artifact path was missing the comment-author-type != 'Bot' check present on the direct path.
  4. Unguarded consumer template — the pr-review-trigger.yml example in the README shipped with zero bot-skip guard, so copy-paste adopters were unprotected.

Fix

Replace brittle login-literal checks with a multi-identity + body-marker guard. The bot always injects <!-- cagent-review --> or <!-- cagent-review-reply --> into its comments; checking for those markers makes the guard self-healing across identity changes (login renames, App → PAT switches, etc.).

Changed files

File Change
.github/workflows/self-review-pr-trigger.yml Extended save-context job guard: adds docker-agent[bot], user.type != 'Bot', and both HTML marker checks alongside the existing login check. pull_request events (where comment is null) still pass correctly — null != 'docker-agent' evaluates TRUE.
.github/workflows/self-review-pr.yml Added actor + marker guard to the issue_comment branch of the review job (previously had no actor check at all).
.github/workflows/review-pr.yml Three sub-fixes: (a) added actor + marker guard to review job issue_comment branch; (b) tightened pull_request non-review_requested branch from sender.type != 'Bot' to also reject sender.login == 'docker-agent' / 'docker-agent[bot]'; (c) added missing comment-author-type != 'Bot' to reply-to-feedback artifact path for parity with the direct path.
review-pr/README.md Added the same if: guard to the save-context job in the pr-review-trigger.yml consumer template.

Behaviour preserved

  • pull_request events (no comment payload) are unaffected — the null-safe comparisons evaluate TRUE and the jobs still run.
  • review_requested events targeting docker-agent still trigger the review.
  • /review and @docker-agent mention paths are unchanged.
  • All 331 unit tests pass. Biome + tsc + actionlint clean.

Refs: investigation artifact c1da342d-cc21-49a0-bca6-364151e22183

Guards previously relied on a single bare-string literal ('docker-agent')
and/or the sender.type == 'Bot' heuristic. PAT-based bots have type 'User',
so those checks were silently missed. Additionally several workflow branches
had no actor check at all.

Changes:
- self-review-pr-trigger.yml: extend save-context job guard to also reject
  'docker-agent[bot]', any user.type == 'Bot', and comments bearing the
  <!-- cagent-review --> or <!-- cagent-review-reply --> HTML markers.
  The marker check is self-healing across identity changes.

- self-review-pr.yml: add actor + marker guard to the issue_comment branch
  of the review job (previously had no actor check at all).

- review-pr.yml (review job, issue_comment branch): add the same actor +
  marker guard. The bot's own comments can no longer queue a review run.

- review-pr.yml (review job, pull_request non-review_requested branch):
  tighten sender check from type != 'Bot' to also reject
  sender.login == 'docker-agent' and 'docker-agent[bot]', covering PAT
  machine-user accounts whose type is 'User'.

- review-pr.yml (reply-to-feedback artifact path): add missing
  comment-author-type != 'Bot' check to match the parity of the direct
  path and reply-to-mention's artifact branch.

- review-pr/README.md: add the same job-level if: guard to the
  pr-review-trigger.yml consumer template so copy-paste deployments are
  protected out of the box.
Copy link
Copy Markdown
Contributor

@docker-agent docker-agent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

The multi-identity + body-marker guard approach is sound and correctly handles all identified bot identity variants. The logic has been reviewed across all four changed files:

.github/workflows/self-review-pr-trigger.yml — The expanded if: condition correctly guards against pull_request_review_comment events from bots. For pull_request events, github.event.comment is null, causing all conditions to evaluate true — this is intentional and documented behavior; those events are meant to pass through.

.github/workflows/self-review-pr.yml — The previously unguarded issue_comment branch now has the full multi-identity + marker check. The workflow_run.conclusion == 'success' branch correctly relies on upstream filtering in self-review-pr-trigger.yml.

.github/workflows/review-pr.yml — Three sub-fixes applied correctly: (a) issue_comment branch guard added; (b) pull_request non-review_requested branch now also checks sender login for both docker-agent and docker-agent[bot]; (c) artifact path for reply-to-feedback gains the missing comment-author-type != 'Bot' check. The direct pull_request_review_comment path on reply-to-feedback (unchanged) already relies on user.type != 'Bot' to cover GitHub App identities, since docker-agent[bot] always has type == 'Bot'.

review-pr/README.md — Consumer template now ships with the correct guard, eliminating the copy-paste vulnerability.

No bugs found in the changed code.

Copy link
Copy Markdown
Contributor Author

@derekmisler derekmisler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

The bot-loop prevention guards are logically correct. Core behaviours verified:

  • pull_request event path preserved: For pull_request events, github.event.comment is an empty context object. All guard conditions ('' != 'docker-agent', !contains('', '...')) correctly evaluate to true, so legitimate pull_request triggers are unblocked. ✅
  • review_requested path unchanged: The review_requested branch (github.event.requested_reviewer.login == 'docker-agent') is untouched. ✅
  • workflow_run branch in self-review-pr.yml unaffected: The github.event.workflow_run.conclusion == 'success' branch is preserved and unguarded — correct. ✅
  • reply-to-feedback trigger-path gets comment-author-type guard: The previously unguarded trigger path now checks comment-author-type != 'Bot'. ✅

Additional note (not in diff): The README's consumer pr-review.yml caller template (review-pr/README.md line ~106) still has an unguarded issue_comment branch:

if: |
  github.event_name == 'issue_comment' ||
  github.event.workflow_run.conclusion == 'success'

Bot comments from docker-agent will still launch the consumer workflow, only to be filtered internally by the reusable review-pr.yml. The internal self-review-pr.yml was updated (✅) but the public README template was not. Low priority — no incorrect reviews are posted — but worth updating for consistency and to avoid wasted CI minutes.

One inline comment below for a minor consistency gap.

Comment thread .github/workflows/review-pr.yml Outdated
- review-pr.yml: reply-to-feedback artifact path now also rejects
  comment-author == 'docker-agent[bot]', matching the parity pattern
  used in all other multi-identity guards throughout the file.

- review-pr/README.md: the consumer pr-review.yml caller template's
  review job if: condition now mirrors self-review-pr.yml — the bare
  'issue_comment' branch is replaced with a 6-condition guard that
  rejects both docker-agent login variants, type==Bot accounts, and
  any comment bearing the <!-- cagent-review --> or
  <!-- cagent-review-reply --> HTML markers.
@derekmisler derekmisler marked this pull request as ready for review May 12, 2026 22:03
Copy link
Copy Markdown
Contributor

@docker-agent docker-agent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

The bot-loop prevention changes look correct and complete. All four files consistently apply the same layered guard: login checks for both docker-agent and docker-agent[bot], a user.type != 'Bot' check, and HTML-marker checks for both <!-- cagent-review --> and <!-- cagent-review-reply -->.

Key observations:

  • contains() calls are scoped only to issue_comment branches where comment.body is always non-null — null-safety is preserved.
  • pull_request events (null comment) are unaffected: the issue_comment branch is skipped entirely, and the pull_request branches do not reference comment.body.
  • The review_requested path correctly remains unguarded (bots requesting a review should still trigger reviews).
  • The previously asymmetric reply-to-feedback artifact path now has comment-author-type != 'Bot' and comment-author != 'docker-agent[bot]' on parity with the direct path.
  • The README consumer template is updated consistently with the live workflow guards.

@derekmisler derekmisler self-assigned this May 13, 2026
@derekmisler derekmisler requested a review from a team May 14, 2026 13:04
Copy link
Copy Markdown
Contributor

@docker-agent docker-agent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

This PR correctly addresses the bot-triggered PR review loop problem through a multi-layered guard strategy. The changes are well-reasoned and the implementation is sound.

Changes reviewed:

  • .github/workflows/review-pr.yml — Added full actor + marker guard to issue_comment branch of review job; tightened pull_request branch with sender.login checks; added comment-author-type != 'Bot' parity to reply-to-feedback artifact path
  • .github/workflows/self-review-pr-trigger.yml — Extended save-context guard with docker-agent[bot], user.type != 'Bot', and both HTML marker checks
  • .github/workflows/self-review-pr.yml — Added actor + marker guard to previously unguarded issue_comment branch
  • review-pr/README.md — Consumer template updated with matching guard

Key correctness observations:

  • Null-safety on pull_request events: The self-review-pr-trigger.yml fires on pull_request events (no comment payload). Accessing github.event.comment.* on these events returns '' — so '' != 'docker-agent' and !contains('', '<!-- cagent-review -->') both evaluate true, correctly allowing the save-context job to run for real PR events while blocking bot review comments.
  • reply-to-feedback parity: The artifact path (line 381) now matches the direct path's defense posture. The direct path already had user.type != 'Bot' which covers docker-agent[bot] (whose type == 'Bot'), so no gap exists.
  • workflow_run branch: The self-review-pr.yml workflow_run.conclusion == 'success' branch relies on the upstream trigger workflow's guard (now patched), which is the correct architectural pattern — no regression.
  • Body-marker defense: Using <!-- cagent-review --> / <!-- cagent-review-reply --> markers as the primary self-healing guard is robust across identity changes (login renames, App → PAT switches).

No bugs found in the changed code.

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.

2 participants