Optimize review: small-PR and incremental fast paths - #4
Conversation
PRs are now routed to the cheapest review path: - Small PRs (<10 lines): single Opus call (1 invocation vs 4) - Re-reviews (prior marker at different SHA): single Opus call with prior review context, focused on what changed since last review - First review of non-small PRs: full 3-member council (unchanged) New files: - prompts/single-review.md: combined council+synth prompt for single reviewer mode, handles both small and incremental paths New config: - SMALL_PR_THRESHOLD repo variable (default 10) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR optimizes the PR-review agent’s orchestration path to reduce cost/latency by using a single-reviewer “fast path” for small PRs and for re-reviews (when a prior review marker exists), while keeping the existing full 3-member council flow for first reviews of larger PRs.
Changes:
- Add review-mode detection (
small|incremental|full) in the per-PR orchestrator script and short-circuit to a single Opus invocation for non-fullmodes. - Introduce a new
prompts/single-review.mdprompt that combines council + synthesizer behavior into one agent for small/incremental reviews. - Document and wire up
SMALL_PR_THRESHOLDas a workflow/repo variable.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| scripts/review-one-pr.sh | Adds mode selection and a single-reviewer execution path before the existing council workflow. |
| prompts/single-review.md | New prompt defining the single-reviewer behavior, including incremental re-review behavior using prior review context. |
| AGENT.md | Updates documentation to describe the new optimized review modes and the new threshold variable. |
| .github/workflows/pr-review.yml | Exposes SMALL_PR_THRESHOLD to the job environment with a default. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # - "small" : PR has < SMALL_PR_THRESHOLD lines changed → single Opus call | ||
| # - "incremental" : prior review exists at different SHA → single Opus call with prior context | ||
| # - "full" : first review of a non-small PR → 3-member council + synthesizer | ||
| SMALL_PR_THRESHOLD="${SMALL_PR_THRESHOLD:-10}" | ||
| PR_SIZE=$(gh pr view "$PR_URL" --json additions,deletions --jq '.additions + .deletions') | ||
| export PR_SIZE | ||
|
|
||
| if [ "$PR_SIZE" -lt "$SMALL_PR_THRESHOLD" ]; then | ||
| REVIEW_MODE="small" | ||
| elif [ -n "${EXISTING_MARKER_SHA:-}" ]; then | ||
| REVIEW_MODE="incremental" |
| SMALL_PR_THRESHOLD="${SMALL_PR_THRESHOLD:-10}" | ||
| PR_SIZE=$(gh pr view "$PR_URL" --json additions,deletions --jq '.additions + .deletions') | ||
| export PR_SIZE | ||
|
|
||
| if [ "$PR_SIZE" -lt "$SMALL_PR_THRESHOLD" ]; then | ||
| REVIEW_MODE="small" | ||
| elif [ -n "${EXISTING_MARKER_SHA:-}" ]; then |
| # Extract the full body of the review/comment containing our marker for the prior SHA. | ||
| PRIOR_REVIEW_BODY=$( | ||
| gh pr view "$PR_URL" --json reviews,comments \ | ||
| --jq "((.reviews // []) + (.comments // [])) | .[].body | select(. != null) | select(test(\"sha=$PRIOR_REVIEW_SHA\"))" 2>/dev/null \ | ||
| | head -1 || true |
| # Extract the full body of the review/comment containing our marker for the prior SHA. | ||
| PRIOR_REVIEW_BODY=$( | ||
| gh pr view "$PR_URL" --json reviews,comments \ | ||
| --jq "((.reviews // []) + (.comments // [])) | .[].body | select(. != null) | select(test(\"sha=$PRIOR_REVIEW_SHA\"))" 2>/dev/null \ | ||
| | head -1 || true | ||
| ) | ||
| export PRIOR_REVIEW_BODY | ||
| echo " prior review SHA: $PRIOR_REVIEW_SHA (body: ${#PRIOR_REVIEW_BODY} chars)" |
| - If `isDraft` → skip. Print `{"pr":"...","decision":"skip","reason":"draft"}` and exit. | ||
| - Verify `headRefOid == $PR_HEAD_SHA`. If not → skip with `"reason":"head-sha-changed"`. | ||
| 2. `gh pr diff "$PR_URL"` — read the diff. | ||
| - **Incremental mode**: also get the diff since the prior review: |
Haiku triage → Sonnet deep review → Opus security audit, where each tier only fires if the previous one escalated. This replaces the 3-parallel-members + synthesizer architecture. Cost impact: - ~80% of PRs: Haiku + Opus confirm (2 calls, ~30s vs 4 calls, ~5 min) - ~15%: + Sonnet (3 calls, ~2.5 min) - ~5%: full cascade (4 calls, ~5.5 min) Haiku works by receiving pre-fetched context (no tool use needed), which sidesteps the agentic limitations found earlier. New files: - prompts/triage.md — Tier 1: Haiku fast classification - prompts/deep-review.md — Tier 2: Sonnet full review - prompts/security-audit.md — Tier 3: Opus security audit - prompts/cascade-action.md — posts review from any resolving tier Re-reviews (prior marker at different SHA) pass prior review context to the triage, enabling faster incremental checks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
don-petry
left a comment
There was a problem hiding this comment.
Automated review — NEEDS HUMAN REVIEW
Risk: MEDIUM
Reviewed commit: bab010f29438d21527bddf9c045733346b603b11
Council vote: security=MEDIUM · correctness=MEDIUM · maintainability=MEDIUM
Summary
The PR replaces the parallel 3-member review council with a cascading Haiku→Sonnet→Opus tier architecture, but the two commits contain two sequential and conflicting designs — the final HEAD is entirely undocumented in the PR description and test plan. Three functional bugs exist at HEAD: the repository JSON field is invalid for gh pr view (causing silent empty metadata), REVIEW_MODE="triage-approved" is unhandled in single-review.md, and PRIOR_REVIEW_BODY/PRIOR_REVIEW_SHA extraction uses inconsistent head/tail selectors. A dangling reference to the retired synthesize.md breaks the escalation delegation path in single-review mode.
Linked issue analysis
No linked issue found in PR metadata.
Findings
Major
- [major]
scripts/review-one-pr.sh:608(correctness) —repositoryis not a validgh pr view --jsonfield; valid alternatives areheadRepository/headRepositoryOwner. The|| echo '{}'fallback silently gives downstream agents an empty metadata object, stripping all PR context and likely causing spurious escalations. Same invalid field appears inprompts/deep-review.mdandprompts/security-audit.md. - [major]
scripts/review-one-pr.sh:641(correctness) —REVIEW_MODE="triage-approved"set by the orchestrator is not handled inprompts/single-review.md, which only documentssmallandincrementalmodes. The template renders a literal<small-pr|incremental>placeholder and incremental delta analysis is skipped. - [major]
prompts/single-review.md:483(correctness) — Dangling reference: instructs agents to followsynthesize.md step 9a, butsynthesize.mdis being retired by this PR in favour ofcascade-action.md. The escalation delegation path is undefined in single-review mode. - [major]
scripts/review-one-pr.sh:591(correctness) —PRIOR_REVIEW_BODYis selected withhead -1(oldest match) whilePRIOR_REVIEW_SHAreflects the most-recent marker. Under any multi-review history these point to different review cycles, giving agents stale body context paired with the current SHA. Should usetail -1for the body selector.
Minor
- [minor]
scripts/review-one-pr.sh:89(security) —PRIOR_REVIEW_BODYexport creates a prompt-injection surface: any commenter could post a fake<!-- pr-review-agent v1 sha=<SHA> -->marker followed by crafted content ingested as prior review context. Consider validating the full HTML comment structure in the match. - [minor]
prompts/council/(maintainability) — Orphaned prompt files (correctness.md,maintainability.md,security.md) are no longer referenced by the script but were not deleted. - [minor]
scripts/review-one-pr.sh(maintainability) —SONNET_RC=$?captures the Sonnet exit code but is never referenced; the script checks the output file instead. Dead variable. - [minor] (correctness) — PR title and description describe the commit-1 architecture (small-PR threshold, incremental fast path); final HEAD replaced that with the cascade tier system. The test plan items do not match the final code.
- [minor] (correctness) — No CI checks ran against HEAD (
statusCheckRollupis empty). There is no automated test suite verifying the script logic or prompt correctness.
Info
- [info]
.github/workflows/pr-review.yml:48(security · maintainability) —SMALL_PR_THRESHOLDis exposed in the workflowenv:block (safe, admin-controlled) but the cascade script never reads it. Dead configuration from the first commit's approach. - [info] (security) — Haiku triage runs with
--permission-mode plan(no tool access) — good least-privilege practice. - [info] (security) —
jqfilter interpolation of$PRIOR_REVIEW_SHAis safe; value is constrained to[a-f0-9]+. - [info]
prompts/deep-review.md,prompts/security-audit.md(maintainability) — These prompts instruct agents to rungh pr view --json ...repository..., an invalid field. Agents following these prompts will encounter metadata fetch errors. - [info] (maintainability) — Tier-2 Sonnet failure exits hard (
exit 1) without posting any review, while Haiku failure falls back gracefully to escalation. Consider symmetric fallback behaviour. - [info] (correctness) — 5 Copilot inline comments (COMMENTED state) remain unaddressed at HEAD, including the
head -1vstail -1mismatch and arepos/<owner>/<repo>placeholder insingle-review.md.
CI status
No CI checks ran against HEAD commit bab010f29438d21527bddf9c045733346b603b11 — statusCheckRollup is empty.
Reviewed automatically by the don-petry PR-review council (security: opus 4.6 · correctness: sonnet 4.6 · maintainability: sonnet 4.6 · synthesis: sonnet 4.6). The marker on line 1 lets the agent detect new commits and re-review. Reply with @don-petry if you need a human.
Review council — fix requested (cycle 1/3)The automated review council identified the following issues. Please address each one: Findings to fix
Additional tasks
The review council will automatically re-review after new commits are pushed. |
Major fixes: - Replace invalid 'repository' gh field with 'headRepository'/'headRepositoryOwner' across script and all prompts - Add 'triage-approved' as valid REVIEW_MODE in single-review.md with dedicated mode description - Fix PRIOR_REVIEW_BODY selector: head -1 → tail -1 to match most-recent SHA - Update dangling 'synthesize.md step 9a' reference to cascade-action.md Minor fixes: - Validate PRIOR_REVIEW_BODY contains marker before exporting (anti-injection) - Write prior review body to temp file to avoid E2BIG on large reviews - Remove dead SONNET_RC=$? variable; add || true for set -e safety - Delete orphaned prompts/council/ files (no longer referenced by cascade) - Fix incremental-mode compare command: document how to derive owner/repo from headRepository metadata Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Question 4 was ambiguous — users confused "Max miles?" (meant odometer) with search radius. Now asks zip and radius as separate questions (#4 and #5) and renames the variable to MAX_ODOMETER with explicit "(highest mileage on the car itself)" clarification. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add review mode optimization: small-PR and incremental fast paths PRs are now routed to the cheapest review path: - Small PRs (<10 lines): single Opus call (1 invocation vs 4) - Re-reviews (prior marker at different SHA): single Opus call with prior review context, focused on what changed since last review - First review of non-small PRs: full 3-member council (unchanged) New files: - prompts/single-review.md: combined council+synth prompt for single reviewer mode, handles both small and incremental paths New config: - SMALL_PR_THRESHOLD repo variable (default 10) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Replace parallel council with cascading review tiers Haiku triage → Sonnet deep review → Opus security audit, where each tier only fires if the previous one escalated. This replaces the 3-parallel-members + synthesizer architecture. Cost impact: - ~80% of PRs: Haiku + Opus confirm (2 calls, ~30s vs 4 calls, ~5 min) - ~15%: + Sonnet (3 calls, ~2.5 min) - ~5%: full cascade (4 calls, ~5.5 min) Haiku works by receiving pre-fetched context (no tool use needed), which sidesteps the agentic limitations found earlier. New files: - prompts/triage.md — Tier 1: Haiku fast classification - prompts/deep-review.md — Tier 2: Sonnet full review - prompts/security-audit.md — Tier 3: Opus security audit - prompts/cascade-action.md — posts review from any resolving tier Re-reviews (prior marker at different SHA) pass prior review context to the triage, enabling faster incremental checks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix review council findings: 4 major + 3 minor issues Major fixes: - Replace invalid 'repository' gh field with 'headRepository'/'headRepositoryOwner' across script and all prompts - Add 'triage-approved' as valid REVIEW_MODE in single-review.md with dedicated mode description - Fix PRIOR_REVIEW_BODY selector: head -1 → tail -1 to match most-recent SHA - Update dangling 'synthesize.md step 9a' reference to cascade-action.md Minor fixes: - Validate PRIOR_REVIEW_BODY contains marker before exporting (anti-injection) - Write prior review body to temp file to avoid E2BIG on large reviews - Remove dead SONNET_RC=$? variable; add || true for set -e safety - Delete orphaned prompts/council/ files (no longer referenced by cascade) - Fix incremental-mode compare command: document how to derive owner/repo from headRepository metadata Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: DJ <dj@Rachels-Air.localdomain> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Question 4 was ambiguous — users confused "Max miles?" (meant odometer) with search radius. Now asks zip and radius as separate questions (#4 and #5) and renames the variable to MAX_ODOMETER with explicit "(highest mileage on the car itself)" clarification. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add review mode optimization: small-PR and incremental fast paths PRs are now routed to the cheapest review path: - Small PRs (<10 lines): single Opus call (1 invocation vs 4) - Re-reviews (prior marker at different SHA): single Opus call with prior review context, focused on what changed since last review - First review of non-small PRs: full 3-member council (unchanged) New files: - prompts/single-review.md: combined council+synth prompt for single reviewer mode, handles both small and incremental paths New config: - SMALL_PR_THRESHOLD repo variable (default 10) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Replace parallel council with cascading review tiers Haiku triage → Sonnet deep review → Opus security audit, where each tier only fires if the previous one escalated. This replaces the 3-parallel-members + synthesizer architecture. Cost impact: - ~80% of PRs: Haiku + Opus confirm (2 calls, ~30s vs 4 calls, ~5 min) - ~15%: + Sonnet (3 calls, ~2.5 min) - ~5%: full cascade (4 calls, ~5.5 min) Haiku works by receiving pre-fetched context (no tool use needed), which sidesteps the agentic limitations found earlier. New files: - prompts/triage.md — Tier 1: Haiku fast classification - prompts/deep-review.md — Tier 2: Sonnet full review - prompts/security-audit.md — Tier 3: Opus security audit - prompts/cascade-action.md — posts review from any resolving tier Re-reviews (prior marker at different SHA) pass prior review context to the triage, enabling faster incremental checks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix review council findings: 4 major + 3 minor issues Major fixes: - Replace invalid 'repository' gh field with 'headRepository'/'headRepositoryOwner' across script and all prompts - Add 'triage-approved' as valid REVIEW_MODE in single-review.md with dedicated mode description - Fix PRIOR_REVIEW_BODY selector: head -1 → tail -1 to match most-recent SHA - Update dangling 'synthesize.md step 9a' reference to cascade-action.md Minor fixes: - Validate PRIOR_REVIEW_BODY contains marker before exporting (anti-injection) - Write prior review body to temp file to avoid E2BIG on large reviews - Remove dead SONNET_RC=$? variable; add || true for set -e safety - Delete orphaned prompts/council/ files (no longer referenced by cascade) - Fix incremental-mode compare command: document how to derive owner/repo from headRepository metadata Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: DJ <dj@Rachels-Air.localdomain> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Question 4 was ambiguous — users confused "Max miles?" (meant odometer) with search radius. Now asks zip and radius as separate questions (#4 and #5) and renames the variable to MAX_ODOMETER with explicit "(highest mileage on the car itself)" clarification. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
SMALL_PR_THRESHOLDrepo variable (default 10)Cost impact
.github#101): ~1 min instead of ~5 min, 1 invocation instead of 4Test plan
mode: small|incremental|full)🤖 Generated with Claude Code