Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
153 commits
Select commit Hold shift + click to select a range
924e67c
Add scheduled PR review agent
Apr 8, 2026
dcef6e6
Use Claude Code OAuth token instead of API key
Apr 9, 2026
442390f
Enforce single-PR scope and default to dry-run
Apr 9, 2026
cbbc998
Add 3-model council + commit-SHA-aware iterative review
Apr 9, 2026
8278dc2
Upgrade maintainability lens from Haiku 4.5 to Sonnet 4.6
Apr 9, 2026
7457d5e
Go-live improvements for PR review agent (#2)
don-petry Apr 9, 2026
ffec660
Add Claude delegation, auto-merge, and rebase handling (#3)
don-petry Apr 10, 2026
602006d
Optimize review: small-PR and incremental fast paths (#4)
don-petry Apr 10, 2026
2b221f6
Add Claude Code workflow (#8)
don-petry Apr 16, 2026
73be041
feat: add Copilot engine support via REVIEW_ENGINE toggle (#5)
don-petry Apr 16, 2026
d8aeaf3
feat: inline Claude Code workflow to access repo secrets directly (#9)
don-petry Apr 16, 2026
01ae188
feat: cross-engine adversarial rubber duck review (#11)
don-petry Apr 16, 2026
4daec8d
docs: clarify PR review batching issue
Apr 16, 2026
ffa270e
feat: CI gate + poll-loop rebase wait across all review prompts (#14)
don-petry Apr 18, 2026
4ed4a95
docs: update repo references from self to pr-review-agent
Apr 21, 2026
0b2fba3
feat: add car-hunt skill for used vehicle research and ranking
Apr 25, 2026
f27df5f
fix: fix FB Marketplace links, improve AutoTrader fetch, add Sheets e…
Apr 25, 2026
31150f1
fix: add Craigslist as explicit source, enforce real-URL-only link rule
Apr 25, 2026
b8ea0b1
fix: split zip/radius questions, rename MAX_MILES to MAX_ODOMETER
Apr 25, 2026
a645a43
fix: include self-authored PRs awaiting review in agent enumeration (…
don-petry Apr 25, 2026
a4f8328
feat: add doors/transmission fields, update-mode sheet, fix CarGurus/…
Apr 25, 2026
c5a2509
feat: add hard disqualification filters for finance-speak, title issu…
Apr 25, 2026
6013296
feat: add Distance to Car field using Nominatim geocoding + Haversine…
Apr 25, 2026
4e94387
fix: address low result count — batch URL extraction, preview-filter …
Apr 25, 2026
f3ca25c
fix: make cascade-action prompt explicit about gh pr review --approve
Apr 25, 2026
8b45bae
add: cleanup script to fix PRs with comment approvals instead of revi…
Apr 25, 2026
f296628
fix: cleanup script now requires bot account token
Apr 25, 2026
75d73a3
add: workflow to fix stuck PRs using bot account token
Apr 25, 2026
a7e06b4
fix: pass GH_PAT to checkout action so workflow uses bot account
Apr 25, 2026
74a732f
debug: simplify account check, add authentication debug output
Apr 25, 2026
7e072fe
docs: add comprehensive petry-review-bot setup instructions
Apr 25, 2026
e23395c
docs: add GitHub App setup guide (recommended approach)
Apr 26, 2026
67a9294
docs: add comprehensive GitHub App setup documentation
Apr 26, 2026
6eb4df4
fix: silence auth check in fix-stuck-prs when using GitHub App token
Apr 26, 2026
f44c61c
fix: use explicit author instead of @me in stuck PR search
Apr 26, 2026
5e0ed0a
fix: avoid subshell in while loop to preserve variable state
Apr 26, 2026
4005a96
docs: add comprehensive status report for GitHub App setup
Apr 27, 2026
b1f642e
docs: document stuck PR cleanup status and script fixes
Apr 28, 2026
b3b8cbf
docs: add investigation report for PR review agent workflow failures
Apr 28, 2026
3a43005
script: add backfill-approvals to retroactively apply real approvals
Apr 28, 2026
7e83927
feat: add backfill-approvals workflow to run as GitHub App bot
Apr 28, 2026
c7bcac3
fix: convert inner while loop to process substitution to preserve cou…
Apr 28, 2026
5757087
car-hunt: add VIN-deep-dive flow, printable checklists, location-tier…
Apr 28, 2026
c9ecb9d
fix: re-approve PRs where approval predates last commit (stale approval)
Apr 28, 2026
32be8cb
fix: robustly extract JSON verdict from mixed claude --print output (…
don-petry Apr 28, 2026
fc1aa58
fix: extract_verdict_json — check dest file first (agent Bash-write s…
don-petry Apr 29, 2026
659ce68
Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)
don-petry Apr 29, 2026
7e589ee
Remove car-hunt skill and scheduled tasks (moved to don-petry/don-pet…
don-petry Apr 30, 2026
c042ac9
fix: harden Copilot fallback path and remove stale worktrees (#25)
claude[bot] May 3, 2026
885f177
ci: add pre-flight dedup check to prevent duplicate claude-issue PRs …
don-petry May 3, 2026
4dd68db
refactor: migrate to machine user PAT auth (closes #27) (#28)
don-petry May 5, 2026
cf49519
refactor: merge approval repair scripts into single automation
don-petry May 5, 2026
dbd0cc4
feat: add @mention trigger for on-demand PR reviews (#30)
don-petry May 6, 2026
6209fe0
refactor: migrate to org-wide .github-private convention (#41)
don-petry May 7, 2026
05e8fb1
ci: add dependabot-automerge.yml workflow (#73)
don-petry May 10, 2026
5995e8a
fix(pr-review): unblock queue starvation from self-authored PRs (#96)…
don-petry May 10, 2026
743ebc1
chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)
don-petry May 10, 2026
a09f9dc
chore: remove frameworks directory (#101)
don-petry May 10, 2026
c0741de
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
0d86030
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
e414def
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
6b1e240
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
2b0a275
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
793d46a
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
d116adc
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
6a38187
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
ebbe854
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
15d0d6d
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
3b40a38
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
3bf3ec0
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
268e2f3
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
7b0820d
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
6216922
revert: restore all erroneously deleted files (items 4-34) (#104)
don-petry May 11, 2026
43d69cb
feat: add Gemini Pro support and optimize PR review fallback chain (#…
don-petry May 11, 2026
ea73bd8
fix(claude): sync inlined workflow with org standard (#109)
don-petry May 11, 2026
c517bf2
ci: replace inlined claude.yml with standard thin-caller stub
don-petry May 11, 2026
a10924b
ci: pin dependabot-automerge reusable workflow to SHA (#130)
don-petry May 12, 2026
dacb964
fix: restore pr-review.yml content (accidentally emptied in revert)
don-petry May 13, 2026
7c9a627
fix: restore pr-review.yml (file was accidentally emptied)
don-petry May 13, 2026
5ccf819
chore(deps): bump petry-projects/.github/.github/workflows/dependency…
dependabot[bot] May 13, 2026
db2dd06
chore(deps): bump petry-projects/.github/.github/workflows/claude-cod…
dependabot[bot] May 13, 2026
09d4450
chore(deps): bump petry-projects/.github/.github/workflows/dependabot…
dependabot[bot] May 13, 2026
70498d7
fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-f…
don-petry May 13, 2026
4726305
chore(deps): bump petry-projects/.github/.github/workflows/dependabot…
dependabot[bot] May 14, 2026
d8a0e34
feat(prompts/dev-lead): add human prompt template
don-petry May 15, 2026
fcc37b2
feat(prompts/dev-lead): add human-pr prompt template
don-petry May 15, 2026
0b9e8d7
feat(dev-lead): implement dev-lead agent Phases 0-6
don-petry May 15, 2026
265803e
feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption…
don-petry May 15, 2026
03fb532
fix(dev-lead): rate-limit detection, separate status, and retry cron …
don-petry May 16, 2026
4884546
fix(pr-review): handle Gemini trust-mode failure and properly route e…
don-petry May 17, 2026
21513bb
fix: resolve YAML syntax error in dev-lead-reusable workflow
May 17, 2026
0d8c1aa
chore: remove Phase 7 shadow period comment from dev-lead.yml
don-petry May 17, 2026
9a3fb59
test: bypass CI check
May 17, 2026
9bcd21e
fix(pr-review): remove CI bypass hack
May 17, 2026
10592c7
fix(dev-lead): detect engine-committed changes and remove commit inst…
May 18, 2026
abc8e30
fix(dev-lead): address PR review findings — untracked files, upstream…
May 18, 2026
0bbbf7a
fix(dev-lead): handle SonarQube Quality Gate failures (issue #279) (#…
don-petry May 19, 2026
626c601
fix(dev-lead): configure git identity, surface commit/push failures, …
don-petry May 19, 2026
863d0fb
fix(dev-lead): resolve review threads after fixing + notify CodeRabbi…
don-petry May 19, 2026
8d045c9
fix(dev-lead): post @coderabbitai resolve on no-changes + enable auto…
don-petry May 20, 2026
96cc34a
fix(dev-lead): relay check_run id, verify engine CLI, improve SonarCl…
don-petry May 20, 2026
8d0da10
ci: add required ci.yml workflow
don-petry May 20, 2026
9540b94
fix(ci): move shopt before yamllint call (fixes syntax error)
don-petry May 20, 2026
ce8a2a9
fix(dev-lead): configure git user identity in fix-issue script (#326)
don-petry May 20, 2026
84b723d
feat: implement issue #232 — Phase 1: Issue Triage gh-aw workflow (#284)
don-petry May 20, 2026
9a1d92f
style(ci): remove trailing blank line to fix yamllint recurring failure
don-petry May 20, 2026
b18f770
feat: implement issue #183 — Compliance: non-stub-claude.yml (#328)
don-petry May 20, 2026
1a8edfa
feat: implement issue #135 — Compliance: unpinned-actions-claude.yml …
don-petry May 21, 2026
62f90b7
feat: implement issue #184 — Compliance: non-stub-dependency-audit.ym…
don-petry May 21, 2026
736e21b
chore(deps): bump petry-projects/.github/.github/workflows/dependency…
dependabot[bot] May 21, 2026
578a6ca
feat(fleet-monitor): summarize token usage by workflow (#343)
don-petry May 21, 2026
bfbdf24
feat(dev-lead): harden agent instructions with Plan→TDD→Verify→Rubber…
don-petry May 23, 2026
ff9769a
add: force-deploy-pr-review workflow for branch-protection bypass
Jun 3, 2026
9a6317b
add: pr-review-reusable.yml wrapper for workflow_call pattern
Jun 4, 2026
cdb3d29
fix: explicitly pass secrets in pr-review-reusable.yml workflow_call
Jun 4, 2026
1a42625
feat: implement issue #425 — dev-lead: Incomplete issue resolution - …
don-petry Jun 4, 2026
8e3f169
chore(deps): bump actions/checkout from 4 to 6 (#433)
dependabot[bot] Jun 4, 2026
97c3e96
merge: PR #353 - PR Review Agent diagnostics and transient failure ha…
Jun 4, 2026
9dfc955
merge: PR #422 - Add deploy-pr-review.yml workflow for branch protect…
Jun 4, 2026
eeaf0aa
fix(ci): strip trailing whitespace from deploy-pr-review workflows (#…
don-petry Jun 6, 2026
b26a00c
fix(engine): handle gemini quota exhaustion and capture stderr (#226)
don-petry Jun 8, 2026
8255a75
feat: implement issue #396 — Compliance: non-stub-dependency-audit.ym…
don-petry Jun 8, 2026
d7307f1
feat: implement issue #438 — Compliance: unpinned-actions-deploy-pr-r…
don-petry Jun 8, 2026
32c2c03
feat: implement issue #404 — PR Review Agent — failures detected 2026…
don-petry Jun 8, 2026
e5790a7
feat: implement issue #511 — [Fleet Monitor] petry-projects/.github-p…
don-petry Jun 10, 2026
94e496a
feat: implement issue #512 — [Fleet Monitor] petry-projects/.github-p…
don-petry Jun 10, 2026
32210e7
feat: implement issue #140 — Compliance: non-stub-dependabot-automerg…
don-petry Jun 10, 2026
c3af009
feat: implement issue #395 — Compliance: non-stub-dev-lead.yml (#488)
don-petry Jun 10, 2026
802ca8d
chore: retire deploy-pr-review, force-deploy, and pr-review-reusable …
don-petry Jun 10, 2026
6edba30
feat: implement issue #545 — Compliance: non-stub-dependency-audit.ym…
don-petry Jun 10, 2026
1ede913
chore(frameworks): vendor BMAD Method v6.8.0 + Test Architect v1.19.0…
don-petry Jun 11, 2026
7395b32
feat: implement issue #244 — [Fleet Monitor] petry-projects/.github-p…
don-petry Jun 11, 2026
d70da86
chore(deps): bump the actions group across 1 directory with 8 updates…
dependabot[bot] Jun 13, 2026
5e2770c
feat: implement issue #582 — [Phase 1] Define held-out eval-case form…
don-petry Jun 14, 2026
1e9cf9b
feat: implement issue #583 — deterministic eval scorer (triage) over …
don-petry Jun 14, 2026
fa74c1d
feat: implement issue #143 — Compliance: secret_scanning_non_provider…
don-petry Jun 16, 2026
4994519
feat: implement issue #695 — [Fleet Monitor] petry-projects/.github-p…
don-petry Jun 17, 2026
d20cb03
feat: implement issue #796 — Compliance: non-stub-dependency-audit.ym…
don-petry Jun 19, 2026
3e82e97
feat: implement issue #797 — Compliance: non-stub-dependabot-automerg…
don-petry Jun 19, 2026
9d9fb45
test(dogfood): comment-only touch to exercise downstream-impact on ne…
don-petry Jun 21, 2026
fd3d29a
revert(dogfood): remove ci-status.sh dogfood comment + re-prove downs…
don-petry Jun 21, 2026
b231930
docs(release): document live dev-lead next/ring channels + staged-rol…
don-petry Jun 21, 2026
c7bfd19
feat: implement issue #898 — pr-review self-review deadlock: `ci-pend…
donpetry-bot Jun 21, 2026
32894a8
chore: apply manual instructions [skip ci-relay]
donpetry-bot Jun 21, 2026
11f9612
chore: apply manual instructions [skip ci-relay]
donpetry-bot Jun 22, 2026
533bfaf
chore: apply manual instructions [skip ci-relay]
donpetry-bot Jun 22, 2026
4fb8d8d
chore: apply manual instructions [skip ci-relay]
donpetry-bot Jun 23, 2026
c9175de
Merge branch 'main' into dev-lead/issue-898-20260621-2309
don-petry Jun 23, 2026
855686e
Merge branch 'main' into dev-lead/issue-898-20260621-2309
don-petry Jun 23, 2026
86f3b22
Merge branch 'main' into dev-lead/issue-898-20260621-2309
don-petry Jun 23, 2026
62930d9
Merge branch 'main' into dev-lead/issue-898-20260621-2309
don-petry Jun 23, 2026
bc2afd8
chore: apply manual instructions [skip ci-relay]
donpetry-bot Jun 23, 2026
052fe1a
chore: apply manual instructions [skip ci-relay]
donpetry-bot Jun 24, 2026
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
84 changes: 84 additions & 0 deletions .github/workflows/actions-fleet-monitor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,90 @@ jobs:
GH_TOKEN: ${{ secrets.GH_PAT_WORKFLOWS }}
run: bash scripts/fleet_monitor.sh

- name: Summarize token usage by workflow
env:
GH_TOKEN: ${{ secrets.GH_PAT_WORKFLOWS }}
REPO: petry-projects/.github-private
LOOKBACK_DAYS: ${{ env.LOOKBACK_DAYS }}
run: |
set -euo pipefail

workdir="$(mktemp -d)"
trap 'rm -rf "$workdir"' EXIT
jsonl_dir="$workdir/jsonl"
artifacts_file="$workdir/token-artifacts.base64"
mkdir -p "$jsonl_dir"

cutoff_epoch="$(date -u -d "-${LOOKBACK_DAYS} days" +%s)"
artifact_count=0

gh api "repos/$REPO/actions/artifacts" --paginate \
--jq '.artifacts[] | select(.name | startswith("token-usage-")) | @base64' \
> "$artifacts_file"

while IFS= read -r encoded; do
[ -n "$encoded" ] || continue
item="$(printf '%s' "$encoded" | base64 -d)"
id="$(printf '%s' "$item" | jq -r '.id')"
created_at="$(printf '%s' "$item" | jq -r '.created_at')"
expired="$(printf '%s' "$item" | jq -r '.expired')"
created_epoch="$(date -u -d "$created_at" +%s)"

if [ "$expired" = "true" ] || [ "$created_epoch" -lt "$cutoff_epoch" ]; then
continue
fi

zip_file="$workdir/artifact-$id.zip"
unzip_dir="$workdir/unzip-$id"
mkdir -p "$unzip_dir"
gh api "repos/$REPO/actions/artifacts/$id/zip" > "$zip_file"
unzip -q -o "$zip_file" -d "$unzip_dir"

found_any=false
while IFS= read -r f; do
found_any=true
cp "$f" "$jsonl_dir/${id}-$(basename "$f")"
done < <(find "$unzip_dir" -type f -name '*.jsonl' -print)

if [ "$found_any" = true ]; then
artifact_count=$((artifact_count + 1))
fi
done < "$artifacts_file"

if ! find "$jsonl_dir" -type f -name '*.jsonl' | grep -q .; then
{
echo "## Token usage (${LOOKBACK_DAYS} day lookback)"
echo
echo "No token-usage artifacts found in the lookback window."
} >> "$GITHUB_STEP_SUMMARY"
exit 0
fi

aggregate="$workdir/token-usage-summary.json"
jq -s '
sort_by(.workflow) |
group_by(.workflow) |
map({
workflow: (.[0].workflow // "unknown"),
calls: length,
input_tokens: (map(.input_tokens // 0) | add),
cache_read_tokens: (map(.cache_read_tokens // 0) | add),
output_tokens: (map(.output_tokens // 0) | add),
et: (map(.et // 0) | add)
}) |
sort_by(.et) | reverse
' "$jsonl_dir"/*.jsonl > "$aggregate"

{
echo "## Token usage (${LOOKBACK_DAYS} day lookback)"
echo
echo "Processed ${artifact_count} token-usage artifact(s)."
echo
echo "| Workflow | Calls | Input Tokens | Cache Read Tokens | Output Tokens | Total ET |"
echo "|---|---:|---:|---:|---:|---:|"
jq -r '.[] | "| \(.workflow) | \(.calls) | \(.input_tokens) | \(.cache_read_tokens) | \(.output_tokens) | \(.et | tostring) |"' "$aggregate"
} >> "$GITHUB_STEP_SUMMARY"

- name: Create issue with findings
if: env.HAS_FAILURES == 'true' && hashFiles('fleet_monitor_report.md') != ''
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ jobs:
tests/test_downstream_impact_assemble.bats \
tests/test_downstream_impact_triage_inline.bats \
tests/test_downstream_impact_regression.bats \
tests/test_engine_unavailable_notice.bats
tests/test_engine_unavailable_notice.bats \
tests/test_batch_skip_reporting.bats \
tests/test_review_one_pr_ci_pending.bats

validate-agent-profiles:
runs-on: ubuntu-latest
Expand Down
65 changes: 61 additions & 4 deletions scripts/review-batch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,30 @@ Every configured review engine (Claude → Copilot → Gemini) reported a usage/
return 0
}

# ── Accurate exit-100 skip reporting (issue #898) ───────────────────────────
# review-one-pr.sh returns 100 for every no-op/skip and prints a JSON verdict
# carrying the *reason* (ci-pending, already-reviewed-at-head, …). To report the
# real reason instead of a blanket "already reviewed" line — which mislabelled a
# CI-pending deferral as a completed review and made the #892 stuck-green case
# look like an idempotency deadlock — capture the reviewer's stdout per PR and
# parse the trailing reason out of it.
run_review_capture() {
local url="$1" rc=0
set +e
bash scripts/review-one-pr.sh "$url" | tee "$REVIEW_OUT"
rc=${PIPESTATUS[0]}
set -e
return "$rc"
}

# Extract the last "reason":"…" value from a captured review log (the verdict
# JSON is the final line review-one-pr.sh emits on a skip). Empty if none.
skip_reason_from() {
grep -oE '"reason"[[:space:]]*:[[:space:]]*"[^"]*"' "$1" 2>/dev/null \
| tail -n1 \
| sed -E 's/.*"reason"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/'
}

# Pre-flight: validate engine availability before touching any PR.
# Sets CLAUDE_AVAILABLE, GEMINI_AVAILABLE, COPILOT_AVAILABLE and emits
# ::warning:: annotations + job-summary table for any unavailable engine.
Expand Down Expand Up @@ -168,6 +192,7 @@ fi

actual=0
skipped_noops=0
deferred=0
failed=0
engine_fallbacks=0
fallback_engines=""
Expand All @@ -177,6 +202,10 @@ abort_pr=""
abort_reason=""
total_candidates=$(grep -c . "$PRS_FILE" || true)

# Per-PR capture buffer for run_review_capture / skip_reason_from (issue #898).
REVIEW_OUT=$(mktemp) || { echo "::error::Failed to create temporary file for review capture" >&2; exit 1; }
trap 'rm -f "$REVIEW_OUT"' EXIT

while IFS= read -r pr_url; do
[ -z "$pr_url" ] && continue
processed=$((processed + 1))
Expand All @@ -192,7 +221,7 @@ while IFS= read -r pr_url; do

echo "::group::Reviewing $pr_url"
rc=0
bash scripts/review-one-pr.sh "$pr_url" || rc=$?
run_review_capture "$pr_url" || rc=$?

# Treat known engine-unavailable setup/runtime errors as fallback-eligible
if [ "$rc" -eq 55 ] || [ "$rc" -eq 127 ]; then
Expand All @@ -210,7 +239,7 @@ while IFS= read -r pr_url; do
engine_fallbacks=$((engine_fallbacks + 1))
fallback_engines="${fallback_engines:+$fallback_engines, }copilot"
rc=0
bash scripts/review-one-pr.sh "$pr_url" || rc=$?
run_review_capture "$pr_url" || rc=$?

# Handle Copilot engine-unavailable setup/runtime errors post-fallback
if [ "$rc" -eq 55 ] || [ "$rc" -eq 127 ]; then
Expand Down Expand Up @@ -243,7 +272,7 @@ while IFS= read -r pr_url; do
engine_fallbacks=$((engine_fallbacks + 1))
fallback_engines="${fallback_engines:+$fallback_engines, }gemini"
rc=0
bash scripts/review-one-pr.sh "$pr_url" || rc=$?
run_review_capture "$pr_url" || rc=$?
fi

case "$rc" in
Expand All @@ -253,7 +282,34 @@ while IFS= read -r pr_url; do
;;
100)
skipped_noops=$((skipped_noops + 1))
echo "::notice::No-op (already reviewed)"
# Report the real skip reason instead of a blanket "already reviewed".
# Reasons that mean "a review is still owed once checks settle" bump the
# deferred counter so the run summary flags them (issue #898).
reason=$(skip_reason_from "$REVIEW_OUT")
case "$reason" in
already-reviewed-at-head)
echo "::notice::No-op — already reviewed at current head" ;;
ci-pending|ci-pending-after-poll)
deferred=$((deferred + 1))
echo "::notice::Deferred — CI still pending; a review is owed once checks go green" ;;
ci-failing)
deferred=$((deferred + 1))
echo "::notice::Deferred — CI failing; will re-review after fixes land" ;;
waiting-for-advisory-bots)
deferred=$((deferred + 1))
echo "::notice::Deferred — awaiting advisory bot reviews" ;;
advisory-gate-api-error)
deferred=$((deferred + 1))
echo "::notice::Deferred — advisory-bot gate API error; will re-evaluate next run" ;;
changes-requested)
echo "::notice::Skipped — human changes requested; awaiting author" ;;
human-escalated|max-cycles-reached)
echo "::notice::Skipped — escalated to a human reviewer" ;;
gh-rate-limited)
echo "::notice::Skipped — gh API rate limited; will retry next run" ;;
*)
echo "::notice::No-op${reason:+ — $reason}" ;;
esac
;;
2)
failed=$((failed + 1))
Expand All @@ -278,6 +334,7 @@ done < "$PRS_FILE"
remaining=$((total_candidates - processed))
summary="Summary: $actual reviews posted, $skipped_noops no-ops skipped, $failed failures"
[ "$engine_fallbacks" -gt 0 ] && summary="$summary, $engine_fallbacks engine fallback(s) to $fallback_engines"
[ "$deferred" -gt 0 ] && summary="$summary ($deferred deferred pending CI/checks — owed a review once green)"
summary="$summary (processed $processed/$total_candidates candidates)"

if [ "$session_aborted" -eq 1 ]; then
Expand Down
Empty file modified scripts/review-one-pr.sh
100644 → 100755
Empty file.
127 changes: 127 additions & 0 deletions tests/test_batch_skip_reporting.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#!/usr/bin/env bats
# Regression tests for accurate exit-100 skip reporting in scripts/review-batch.sh
# (issue #898).
#
# The reviewer (review-one-pr.sh) returns exit 100 for *every* no-op/skip: an
# already-reviewed head, a CI-pending deferral, a CI-failing deferral, a
# human-changes-requested skip, an escalation skip, etc. The batch driver used
# to collapse all of these into a single "No-op (already reviewed)" line. That
# message is actively misleading for a CI-pending deferral — it claims a review
# already happened at the current head when in fact the PR is *owed* a review
# once CI goes green. That mislabelling is what made the #892 "deadlock" look
# like a stuck idempotency marker.
#
# These tests assert the batch reports the *reason* behind an exit-100 skip:
# - a ci-pending skip must NOT claim "already reviewed" and must surface as a
# deferral ("Deferred" / "CI still pending"),
# - an already-reviewed-at-head skip must still say "already reviewed",
# - a batch with at least one CI deferral must note "deferred" in the summary
# so an operator scanning the run knows a review is still owed.
#
# Run with: bats tests/test_batch_skip_reporting.bats

setup() {
export TEST_DIR="$BATS_TMPDIR/batch-skip-report-test"
mkdir -p "$TEST_DIR/scripts"
mkdir -p "$TEST_DIR/bin"
cd "$TEST_DIR"

export PRS_FILE="prs.txt"
echo "https://github.com/fake/pull/1" > "$PRS_FILE"
export CANDIDATE_LIMIT=1
export MAX_PRS=1
export REVIEW_ENGINE="claude"
export PATH="$TEST_DIR/bin:$PATH"

cp "$BATS_TEST_DIRNAME/../scripts/review-batch.sh" "scripts/"

cat > "scripts/validate-engines.sh" <<'EOF'
validate_engines() {
export CLAUDE_AVAILABLE="true"
export GEMINI_AVAILABLE="true"
export COPILOT_AVAILABLE="true"
}
EOF

cat > "scripts/engine.sh" <<'EOF'
export COPILOT_API_MODEL="openai/o4-mini"
EOF

cat > "$TEST_DIR/bin/gh" <<'EOF'
#!/bin/bash
exit 0
EOF
chmod +x "$TEST_DIR/bin/gh"
}

teardown() {
rm -rf "$TEST_DIR"
}

# review-one-pr.sh stub that emits a CI-pending skip exactly like the real
# reviewer's non-force ci-pending gate: a human-readable line plus a JSON
# verdict carrying reason=ci-pending, then exit 100.
_stub_ci_pending() {
cat > "scripts/review-one-pr.sh" <<'EOF'
#!/bin/bash
echo " skip: CI checks still in progress for $1"
echo '{"pr":"'"$1"'","decision":"skip","reason":"ci-pending"}'
exit 100
EOF
chmod +x "scripts/review-one-pr.sh"
}

_stub_already_reviewed() {
cat > "scripts/review-one-pr.sh" <<'EOF'
#!/bin/bash
echo " noop: already reviewed at deadbeef"
echo '{"pr":"'"$1"'","decision":"skip","reason":"already-reviewed-at-head"}'
exit 100
EOF
chmod +x "scripts/review-one-pr.sh"
}

@test "batch: ci-pending skip is not reported as 'already reviewed'" {
_stub_ci_pending

run bash scripts/review-batch.sh
echo "$output" >&2

[ "$status" -eq 0 ]
# The defect: a CI-pending deferral was mislabelled as an already-reviewed
# no-op. The batch must NOT claim the PR was already reviewed.
[[ "$output" != *"already reviewed"* ]]
}

@test "batch: ci-pending skip is reported as a deferral" {
_stub_ci_pending

run bash scripts/review-batch.sh
echo "$output" >&2

[ "$status" -eq 0 ]
[[ "$output" == *"Deferred"* ]]
[[ "$output" == *"CI"* ]]
}

@test "batch: ci-pending deferral is surfaced in the session summary" {
_stub_ci_pending

run bash scripts/review-batch.sh
echo "$output" >&2

[ "$status" -eq 0 ]
[[ "$output" == *"deferred"* ]]
}

@test "batch: already-reviewed-at-head skip still reports 'already reviewed'" {
_stub_already_reviewed

run bash scripts/review-batch.sh
echo "$output" >&2

[ "$status" -eq 0 ]
[[ "$output" == *"already reviewed"* ]]
# An already-reviewed no-op is genuinely done — it is NOT a CI deferral.
[[ "$output" != *"deferred"* ]]
}
Loading
Loading