Skip to content

[bug-hunter] pr-review skip logic misreads paginated bot review state and posts duplicate reviews #1016

Description

@github-actions

Impact

When a PR has bot-authored reviews spread across multiple API pages, claude-workflows/pr-review/scripts/pr-review.sh can fail to recognize that the latest bot verdict already matches the current verdict. The workflow then posts an unnecessary duplicate review instead of skipping.

This creates noisy duplicate reviews and extra workflow churn for users/repositories with long review histories.

Reproduction Steps

  1. Run this standalone repro script:
cat > /tmp/gh-aw/agent/repro-pr-review-last-state.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

EXPECTED_STATE="APPROVED"
PR_REVIEW_BOT_LOGIN="copilot"

PAGINATED_REVIEWS='[{"user":{"login":"copilot"},"state":"COMMENTED"}]
[{"user":{"login":"copilot"},"state":"APPROVED"}]'

LAST_OWN_STATE=$(printf "%s\n" "$PAGINATED_REVIEWS" | jq -r --arg login "$PR_REVIEW_BOT_LOGIN" '[.[] | select(.user.login == $login)] | last | .state // empty')

printf 'LAST_OWN_STATE raw:\n%s\n' "$LAST_OWN_STATE"
printf 'EXPECTED_STATE: %s\n' "$EXPECTED_STATE"

if [ "$LAST_OWN_STATE" = "$EXPECTED_STATE" ]; then
  echo "Comparison result: equal (unexpected for repro)"
  exit 2
else
  echo "Comparison result: not equal (bug reproduced)"
fi
EOF
chmod +x /tmp/gh-aw/agent/repro-pr-review-last-state.sh
/tmp/gh-aw/agent/repro-pr-review-last-state.sh

Expected vs Actual

Expected: The latest bot review state resolves to a single value (APPROVED), so equality check succeeds and the script skips posting a duplicate review when there are no new comments.

Actual: The jq pipeline emits one value per page (COMMENTED and APPROVED), producing a multiline LAST_OWN_STATE; string comparison fails and the script treats verdict as changed.

Observed output:

LAST_OWN_STATE raw:
COMMENTED
APPROVED
EXPECTED_STATE: APPROVED
Comparison result: not equal (bug reproduced)

Failing Test

#!/usr/bin/env bash
set -euo pipefail

EXPECTED_STATE="APPROVED"
PR_REVIEW_BOT_LOGIN="copilot"
PAGINATED_REVIEWS='[{"user":{"login":"copilot"},"state":"COMMENTED"}]
[{"user":{"login":"copilot"},"state":"APPROVED"}]'

LAST_OWN_STATE=$(printf "%s\n" "$PAGINATED_REVIEWS" | jq -r --arg login "$PR_REVIEW_BOT_LOGIN" '[.[] | select(.user.login == $login)] | last | .state // empty')

test "$LAST_OWN_STATE" = "$EXPECTED_STATE"

Evidence

  • claude-workflows/pr-review/scripts/pr-review.sh:159-161
    • gh api .../reviews --paginate | jq ... '[.[] | select(.user.login == $login)] | last | .state // empty'
    • With --paginate, each page is processed independently by jq, so this can emit multiple lines.
  • claude-workflows/pr-review/scripts/pr-review.sh:163-165
    • Multiline LAST_OWN_STATE is compared to single EXPECTED_STATE, causing false mismatch and duplicate review posting behavior.
  • Duplicate check performed: no open/merged issue/PR found for this exact pr-review.sh paginated state parsing bug using GitHub search queries ("pr-review.sh" "--paginate" "reviews", merged PR search with same terms).

Note

🔒 Integrity filter blocked 3 items

The following items were blocked because they don't meet the GitHub integrity level.

  • #399 search_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #705 search_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #600 search_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

What is this? | From workflow: Trigger Bug Hunter

Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.

  • expires on May 1, 2026, 11:43 AM UTC

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions