Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 28 additions & 20 deletions .agents/scripts/review-bot-gate-helper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,11 @@ bot_has_real_review() {

any_bot_has_success_status() {
# GH#3005: When bots are rate-limited in comments but still post a formal
# GitHub status check (e.g., CodeRabbit posts a "coderabbitai" context with
# state=success), treat the PR as reviewed. The bot completed its analysis
# even though the comment was a rate-limit notice.
# GitHub status check, treat the PR as reviewed.
# GH#3007: The status context name may differ from the bot login.
# E.g., bot login "coderabbitai" but status context "CodeRabbit".
# Match bidirectionally: bot_base starts with context OR context
# starts with bot_base (case-insensitive).
local pr_number="$1"
local repo="$2"

Expand All @@ -149,18 +151,19 @@ any_bot_has_success_status() {
return 1
fi

# Get all commit statuses for the head SHA
local statuses
statuses=$(gh api "repos/${repo}/commits/${head_sha}/statuses" \
--paginate --jq '.[] | select(.state == "success") | .context' \
# Get the combined status (singular endpoint = latest per-context state)
# and check-runs unconditionally, then merge both streams.
# GH#3007: /status (singular) returns the latest state per context,
# avoiding stale-success matches from /statuses (plural, full history).
# Pagination ensures we don't miss contexts when >30 statuses exist.
local statuses check_runs
statuses=$(gh api "repos/${repo}/commits/${head_sha}/status?per_page=100" \
--paginate --jq '.statuses[] | select(.state == "success") | .context' \
2>/dev/null || echo "")

if [[ -z "$statuses" ]]; then
# Also check the combined status endpoint (check runs)
statuses=$(gh api "repos/${repo}/commits/${head_sha}/check-runs" \
--paginate --jq '.check_runs[] | select(.conclusion == "success") | .name' \
2>/dev/null || echo "")
fi
check_runs=$(gh api "repos/${repo}/commits/${head_sha}/check-runs?per_page=100" \
--paginate --jq '.check_runs[] | select(.conclusion == "success") | .name' \
2>/dev/null || echo "")
statuses=$(printf '%s\n%s\n' "$statuses" "$check_runs" | grep -v '^$' || true)

if [[ -z "$statuses" ]]; then
return 1
Expand All @@ -169,14 +172,19 @@ any_bot_has_success_status() {
local statuses_lower
statuses_lower=$(echo "$statuses" | tr '[:upper:]' '[:lower:]')

# Check if any known bot has a success status
local bot bot_base
# GH#3007: Match bidirectionally — the status context may be a prefix
# of the bot login (e.g., "coderabbit" vs "coderabbitai") or vice versa.
local bot bot_base ctx
for bot in "${KNOWN_BOTS[@]}"; do
bot_base=$(echo "$bot" | tr '[:upper:]' '[:lower:]')
if echo "$statuses_lower" | grep -qi "$bot_base"; then
echo "Bot '${bot}' has SUCCESS status check on commit ${head_sha:0:8}" >&2
return 0
fi
while IFS= read -r ctx; do
[[ -z "$ctx" ]] && continue
# Bidirectional prefix match: either string starts with the other
if [[ "$bot_base" == "$ctx"* ]] || [[ "$ctx" == "$bot_base"* ]]; then
echo "Bot '${bot}' has SUCCESS status check on commit ${head_sha:0:8} (context: '${ctx}')" >&2
return 0
fi
done <<<"$statuses_lower"
done
return 1
}
Expand Down
51 changes: 32 additions & 19 deletions .github/workflows/review-bot-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
permissions:
pull-requests: read
contents: read
statuses: read

steps:
- name: Check for AI review bot activity
Expand Down Expand Up @@ -134,8 +135,12 @@ jobs:

# Helper: check if any bot posted a SUCCESS commit status check.
# GH#3005: When bots are rate-limited in comments but still post a
# formal GitHub status check (e.g., CodeRabbit posts a "coderabbitai"
# context with state=success), treat the PR as reviewed.
# formal GitHub status check, treat the PR as reviewed.
# GH#3007: The status context name may differ from the bot login.
# E.g., bot login "coderabbitai" but status context "CodeRabbit".
# Match bidirectionally: bot_base starts with context OR context
# starts with bot_base (case-insensitive). This handles both
# "coderabbitai" matching "coderabbit" and vice versa.
# Uses the PR head SHA to query commit statuses.
any_bot_has_success_status() {
local head_sha
Expand All @@ -145,18 +150,19 @@ jobs:
return 1
fi

# Get all commit statuses for the head SHA
local statuses
statuses=$(gh api "repos/${REPO}/commits/${head_sha}/statuses" \
--paginate --jq '.[] | select(.state == "success") | .context' \
# Get the combined status (singular endpoint = latest per-context state)
# and check-runs unconditionally, then merge both streams.
# GH#3007: /status (singular) returns the latest state per context,
# avoiding stale-success matches from /statuses (plural, full history).
# Pagination ensures we don't miss contexts when >30 statuses exist.
local statuses check_runs
statuses=$(gh api "repos/${REPO}/commits/${head_sha}/status?per_page=100" \
--paginate --jq '.statuses[] | select(.state == "success") | .context' \
2>/dev/null || echo "")

if [[ -z "$statuses" ]]; then
# Also check the combined status endpoint (check runs)
statuses=$(gh api "repos/${REPO}/commits/${head_sha}/check-runs" \
--paginate --jq '.check_runs[] | select(.conclusion == "success") | .name' \
2>/dev/null || echo "")
fi
check_runs=$(gh api "repos/${REPO}/commits/${head_sha}/check-runs?per_page=100" \
--paginate --jq '.check_runs[] | select(.conclusion == "success") | .name' \
2>/dev/null || echo "")
statuses=$(printf '%s\n%s\n' "$statuses" "$check_runs" | grep -v '^$' || true)

if [[ -z "$statuses" ]]; then
return 1
Expand All @@ -165,14 +171,21 @@ jobs:
local statuses_lower
statuses_lower=$(echo "$statuses" | tr '[:upper:]' '[:lower:]')

# Check if any known bot has a success status
# Check if any known bot has a success status.
# GH#3007: Match bidirectionally — the status context may be a
# prefix of the bot login (e.g., "coderabbit" vs "coderabbitai")
# or the bot login may be a prefix of the context.
local bot bot_base ctx
for bot in "${KNOWN_BOTS[@]}"; do
local bot_base
bot_base=$(echo "$bot" | tr '[:upper:]' '[:lower:]' | sed 's/\[bot\]$//')
if echo "$statuses_lower" | grep -qi "$bot_base"; then
echo "Bot '${bot}' has SUCCESS status check on commit ${head_sha:0:8}"
return 0
fi
while IFS= read -r ctx; do
[[ -z "$ctx" ]] && continue
# Bidirectional prefix match: either string starts with the other
if [[ "$bot_base" == "$ctx"* ]] || [[ "$ctx" == "$bot_base"* ]]; then
echo "Bot '${bot}' has SUCCESS status check on commit ${head_sha:0:8} (context: '${ctx}')"
return 0
fi
done <<< "$statuses_lower"
done
return 1
}
Expand Down
Loading