Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 8 additions & 4 deletions .github/workflows/pr-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ concurrency:
jobs:
review:
runs-on: ubuntu-latest
timeout-minutes: 30
timeout-minutes: 60
env:
# GH_PAT is a fine-grained PAT with PR read/write across the repos you
# want the agent to act on. Stored as a repo secret.
Expand All @@ -42,7 +42,7 @@ jobs:

steps:
- name: Checkout agent repo
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Install Claude Code CLI
run: npm install -g @anthropic-ai/claude-code
Expand All @@ -54,15 +54,19 @@ jobs:

- name: Enumerate candidate PRs
id: list
env:
# Cap how many PRs one cron tick reviews to stay within timeout.
# Override via repo variable: gh variable set MAX_PRS --body 20 --repo don-petry/self
MAX_PRS: ${{ vars.MAX_PRS || '10' }}
run: |
set -euo pipefail
if [ -n "${PR_URL_OVERRIDE}" ]; then
echo "${PR_URL_OVERRIDE}" > prs.txt
else
bash scripts/list-prs.sh > prs.txt
bash scripts/list-prs.sh | head -n "$MAX_PRS" > prs.txt

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

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

MAX_PRS comes from a repo variable and is passed directly to head -n. If it’s set to a non-integer (e.g., empty/whitespace/"ten"), head will exit non‑zero and (with set -euo pipefail) the workflow will fail. Consider sanitizing/validating MAX_PRS (e.g., ensure it matches ^[0-9]+$ and default to 10 otherwise) before using it, or clamp it to a safe range.

Copilot uses AI. Check for mistakes.
fi
echo "count=$(wc -l < prs.txt | tr -d ' ')" >> "$GITHUB_OUTPUT"
echo "--- candidate PRs ---"
echo "--- candidate PRs (max $MAX_PRS) ---"
cat prs.txt || true

- name: Review each PR (council + synth)
Expand Down
16 changes: 12 additions & 4 deletions AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,21 @@ gh workflow run pr-review.yml --repo don-petry/self -f dry_run=false

## Tuning

- **Risk rules** live in `prompts/review-pr.md`. Edit there.
- **Risk rules** — edit `prompts/shared.md` (taxonomy), or per-lens in
`prompts/council/{security,correctness,maintainability}.md`.
- **Cron frequency** — change the `cron:` line in the workflow file.
- **Scope** — edit `scripts/list-prs.sh` to add/remove queries (e.g. to include
PRs from a specific org, or to exclude certain repos).
- **Max PRs per run** — defaults to 10 per cron tick to stay within the 60-min
job timeout (~5 min per PR with 3 council members). Override:
`gh variable set MAX_PRS --body 15 --repo don-petry/self`
- **Models** — change model IDs in `scripts/review-one-pr.sh` (`run_member`
calls). Current: security=opus-4-6, correctness=sonnet-4-6,
maintainability=sonnet-4-6, synthesis=sonnet-4-6.

## Cost

Hourly cron × ~30 days = ~720 runs/month. If most hours have zero candidate PRs
the cost is just GitHub Actions minutes (~10s each). Anthropic API cost scales
with PR count and diff size — expect a few cents per non-trivial PR review.
Uses the Claude Max plan via OAuth token — no per-token API billing. GitHub
Actions cost is ~720 runs/month (hourly × 30 days). Runs with zero candidate
PRs finish in ~10s. Each PR reviewed costs ~5 min of runner time (4 model
invocations: 3 council + 1 synthesis).
12 changes: 4 additions & 8 deletions scripts/list-prs.sh
Original file line number Diff line number Diff line change
@@ -1,34 +1,30 @@
#!/usr/bin/env bash
# Enumerate open PRs the agent should consider reviewing.
# Enumerate open, non-draft PRs the agent should consider reviewing.
#
# Two buckets, deduped:
# 1. PRs authored by @me across all repos (self-review).
# 2. PRs where @me is a requested reviewer across all repos.
#
# Output: one PR URL per line on stdout.
# Output: one PR URL per line on stdout. Drafts are excluded.

set -euo pipefail

ME="$(gh api user --jq '.login')"

# gh search prs respects $GH_TOKEN. --json url is portable across versions.
authored=$(gh search prs \
--state open \
--author "@me" \
--draft=false \
--limit 100 \
--json url \
--jq '.[].url')

review_requested=$(gh search prs \
--state open \
--review-requested "@me" \
--draft=false \
--limit 100 \
--json url \
--jq '.[].url')

# Skip drafts — agent should not review WIP PRs.
# We re-query each URL's isDraft via gh pr view in the review step too,
# but filtering here saves API calls for the obvious cases.
{
printf '%s\n' "$authored"
printf '%s\n' "$review_requested"
Expand Down
2 changes: 1 addition & 1 deletion scripts/review-one-pr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ echo " head SHA: $PR_HEAD_SHA"
# 2. Idempotency: look for our marker at this SHA in existing reviews+comments
EXISTING_MARKER_SHA=$(
gh pr view "$PR_URL" --json reviews,comments \
--jq '[.reviews[].body, .comments[].body] | .[] | select(. != null)' 2>/dev/null \
--jq '[(.reviews // [])[] | .body // empty, (.comments // [])[] | .body // empty] | .[] | select(. != null)' 2>/dev/null \
| grep -oE '<!-- pr-review-agent v1 sha=[a-f0-9]+' \
| grep -oE '[a-f0-9]+$' \
| tail -1 || true
Expand Down