Repository navigation
fix: validate decision field after extract_verdict_json in single-review loop - #153
Conversation
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe triage-approved single-review flow now validates the extracted verdict's decision against an allowlist of ChangesVerdict Decision Validation
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request updates the PR review script to validate the 'decision' field from the JSON output, ensuring it is one of the allowed values before proceeding. The reviewer suggested trimming whitespace from the extracted decision for better robustness and adding rate-limit detection to handle engine fallbacks correctly.
| if [ "$single_rc" -eq 0 ] && extract_verdict_json "$VERDICT_JSON.raw" "$VERDICT_JSON"; then | ||
| single_ok=true | ||
| break | ||
| EXTRACTED_DECISION=$(jq -r '.decision // ""' "$VERDICT_JSON" 2>/dev/null || true) |
There was a problem hiding this comment.
To improve robustness against potential LLM formatting variability (such as unexpected leading or trailing whitespace in the JSON string), it is recommended to trim the extracted decision value before validation. This ensures that values like " approve" are correctly matched by the case statement.
Additionally, since this script implements a retry loop for LLM-based tasks, ensure that rate limits are detected immediately by checking exit codes and scanning output for rate-limit indicators. Per repository standards, if a rate limit is detected, the script should exit with code 2 to enable engine fallback mechanisms in the calling process.
For a more complete fix, consider centralizing this validation, sanitization, and rate-limit detection within the extract_verdict_json function in scripts/engine.sh so that all tiers benefit.
| EXTRACTED_DECISION=$(jq -r '.decision // ""' "$VERDICT_JSON" 2>/dev/null || true) | |
| EXTRACTED_DECISION=$(jq -r '.decision // ""' "$VERDICT_JSON" 2>/dev/null | tr -d '[:space:]' || true) |
References
- In shell scripts implementing retry loops for LLM-based tasks, detect rate limits immediately and exit with code 2 to enable engine fallback mechanisms.
There was a problem hiding this comment.
Pull request overview
This PR hardens the single-review confirmation path in scripts/review-one-pr.sh by adding semantic validation of the extracted verdict JSON. It prevents batch failures when extract_verdict_json successfully extracts syntactically valid JSON that still contains an empty/invalid "decision" field (e.g., from a premature tool-written placeholder), converting what was previously a hard crash downstream into an in-loop retry.
Changes:
- After
extract_verdict_jsonsucceeds, parse.decisionand validate it is one ofapprove | escalate | skip. - If
.decisionis invalid/empty, emit a clear diagnostic and fall through to the existing retry behavior. - Preserve the existing “no valid JSON” handling path for malformed/unextractable output.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
donpetry-bot
left a comment
There was a problem hiding this comment.
Automated review — APPROVED ✓
Risk: LOW
Reviewed commit: 704c60a4cc12374e03c171ae3c461a7096944063
Review mode: triage-approved (single reviewer)
Summary
This PR hardens the triage-approved single-review retry loop in scripts/review-one-pr.sh by validating that the .decision field extracted from the verdict JSON is one of approve|escalate|skip before accepting the attempt. Previously, extract_verdict_json only checked syntactic validity (jq empty), so a model emitting partial/placeholder JSON with "decision": "" would slip through and crash post-pr-review.sh with ERROR: invalid decision '' (exit 1), failing the entire batch. The fix converts that hard crash into a graceful retry up to $SINGLE_REVIEW_MAX_RETRIES, matching how malformed JSON is already handled.
Linked issue analysis
No issue is linked, but the PR body cites the concrete reproducer: job 75706589647 failing on combsmith/.github#266. The root cause description (Bash-tool partial write of the verdict JSON before the model finishes reasoning) matches the code path being patched, and the diff directly addresses that gap — adding a semantic check after the existing syntactic one.
Findings
- Correctness: The
casestatement covers exactlyapprove|escalate|skip(the valuespost-pr-review.shaccepts), and the wildcard*)path emits a diagnostic without settingsingle_ok=true, so the loop continues to its existing retry/diagnostic block and either retries or fails after$SINGLE_REVIEW_MAX_RETRIES. The restructuredif/elsecorrectly distinguishes "extraction succeeded but decision invalid" from "extraction itself failed," preserving the prior diagnostic for the latter. - Risk surface: Diff is 12 additions / 4 deletions in a single bash file on a non-hot path (the retry loop runs only after triage decides to confirm). No auth, secrets, migrations, schema, dependencies, or GitHub Actions security surface are touched.
- Minor (non-blocking) observations: Gemini's drive-by suggestions (trim whitespace on the extracted decision; reuse rate-limit detection) are nice-to-haves but not required —
jq -rstrips surrounding JSON quoting, and rate-limit handling already runs earlier at lines 354–358 before this block, so it is unaffected. The2>/dev/null || trueon thejqcall is appropriate defensive belt-and-suspenders since the JSON was already validated byextract_verdict_json.
CI status
All green: CodeQL Analyze (actions) SUCCESS, CodeQL SUCCESS, CodeRabbit SUCCESS, SonarQube quality gate passed (0 new issues). CodeRabbit submitted an APPROVED review on the head commit 704c60a4.
Reviewed automatically by the PR-review agent (single-reviewer mode: opus 4.7). Reply if you need a human review.
donpetry-bot
left a comment
There was a problem hiding this comment.
Automated review — APPROVED ✓
Risk: LOW
Reviewed commit: 5b6140f6640cc55207379bcd5be3a20c4ca7d0b3
Review mode: triage-approved (single reviewer)
Summary
Confirmation review of a small (+12/-4) hardening change in scripts/review-one-pr.sh. After extract_verdict_json succeeds, the retry loop now extracts .decision and validates it against the allowlist approve|escalate|skip before setting single_ok=true; non-allowlisted values (including the empty string that triggered the original incident on combsmith/.github#266) fall through to the existing retry with a clear diagnostic instead of crashing post-pr-review.sh with ERROR: invalid decision ''. This PR was already approved at SHA 704c60a4 by donpetry-bot; the new commits since then are merges from main only, so the substantive change is unchanged.
Linked issue analysis
No linked issue is declared, but the PR body cites a concrete reproducer (job 75706589647, PR combsmith/.github#266) and the root cause matches the patched code path — a model writing partial JSON via the Bash tool before reasoning completes. The fix directly closes that gap by adding the semantic check the previous code lacked.
Findings
- Correctness: The
caseallowlist exactly matches the valuespost-pr-review.shaccepts. The wildcard*)branch correctly emits a diagnostic without settingsingle_ok=true, so control falls through to the existing retry/bail block. The restructuredif/elseproperly distinguishes "extraction succeeded but decision invalid" from "extraction itself failed," preserving the previous diagnostic message in the latter case. - Delta since prior review:
git compare 704c60a4...5b6140f6shows only merge-from-main commits (dependabot bumps for reusable workflows, .gitignore push-protection baseline, code-quality ruleset). None touchscripts/review-one-pr.sh. The full PR diff is identical to what was approved at 704c60a. - Risk surface: A single bash file on a non-hot path (only runs after triage decides to confirm). No auth, secrets, migrations, schema, dependencies, or Actions security surface touched.
- Minor (non-blocking): Gemini's drive-by suggestions (whitespace trim, rate-limit detection) are not required —
jq -rstrips JSON quoting, and rate-limit handling runs earlier in the loop and is unaffected by this block.
CI status
All green on 5b6140f6: CodeQL Analyze (actions) SUCCESS, CodeQL SUCCESS, CodeRabbit SUCCESS (APPROVED), agent-shield SUCCESS, dependency-audit SUCCESS, SonarCloud SUCCESS (Quality Gate passed, 0 new issues), unit-tests SUCCESS. Skipped checks are conditional and expected.
Reviewed automatically by the PR-review agent (single-reviewer mode: opus 4.7). Reply if you need a human review.
Superseded by automated re-review at 5b6140f.
donpetry-bot
left a comment
There was a problem hiding this comment.
Automated review — APPROVED ✓
Risk: LOW
Reviewed commit: 2059a3ce81d5be44ac203cb7d3826ca5fe910f9f
Review mode: triage-approved (single reviewer)
Summary
Small defensive fix in scripts/review-one-pr.sh (+12/-4, single file). After extract_verdict_json succeeds, the triage-approved single-review loop now validates .decision against the allowlist approve|escalate|skip before accepting the attempt; an empty/invalid decision falls through to the existing retry path with a clear diagnostic instead of crashing the batch downstream in post-pr-review.sh. Triage classified this PR as low-risk; confirming.
Linked issue analysis
No linked issue, which is acceptable here — the PR body cites the concrete failed job (run 25775325448, job 75706589647) and the exact ERROR: invalid decision '' symptom, then walks through the root cause (model wrote a partial JSON object that was syntactically valid but had decision: ""). The diff matches the description exactly.
Findings
- Code change matches description. The diff applied is exactly the one shown in the PR body. Logic:
caseonEXTRACTED_DECISIONwithapprove|escalate|skip→single_ok=true; break; everything else logs and continues the retry loop.jq -r '.decision // ""'with2>/dev/null || truesafely tolerates missing/non-object JSON. - No new risk surface. Pure validation; no new external calls, no secrets handling, no permission/auth changes.
- Incremental delta since prior approval (
5b6140f) is benign. Only merge commits frommainbringing in.github/workflows/pr-review.ymlchanges (aliasGEMINI_API_KEYfromGOOGLE_API_KEY, raiseCANDIDATE_LIMIT100→250). Thescripts/review-one-pr.shchange itself has not changed since the prior approval. - No unresolved human-reviewer threads. CodeRabbit approved with no actionable comments; gemini-code-assist and Copilot left informational comments only.
CI status
All required checks pass: agent-shield, claude-code / claude, Analyze (actions), dependency-audit / Detect ecosystems, SonarCloud, unit-tests, CodeQL, CodeRabbit, SonarCloud Code Analysis. Skipped checks (dependabot-automerge, conditional claude-code jobs, language-specific dep audits) are expected for this change set. mergeable: MERGEABLE; mergeStateStatus: BLOCKED is the standard required-review gate this approval is intended to satisfy.
Reviewed automatically by the PR-review agent (claude-opus-4-7). Reply if you need a human review.
Superseded by automated re-review at 2059a3c.
041751a to
1a0a357
Compare
The merge-base changed after approval.
|
Auto-rebase failed — merge conflict — this branch has conflicts with Claude will attempt to resolve this automatically. If it cannot, a follow-up comment will explain what needs manual attention. To resolve manually instead: |
|
Auto-rebase failed — merge conflict — this branch has conflicts with Claude will attempt to resolve this automatically. If it cannot, a follow-up comment will explain what needs manual attention. To resolve manually instead: |
|
Auto-rebase failed — merge conflict — this branch has conflicts with Claude will attempt to resolve this automatically. If it cannot, a follow-up comment will explain what needs manual attention. To resolve manually instead: |
|
Auto-rebase failed — merge conflict — this branch has conflicts with Claude will attempt to resolve this automatically. If it cannot, a follow-up comment will explain what needs manual attention. To resolve manually instead: |
|
Auto-rebase failed — merge conflict — this branch has conflicts with Claude will attempt to resolve this automatically. If it cannot, a follow-up comment will explain what needs manual attention. To resolve manually instead: |
|
Auto-rebase failed — merge conflict — this branch has conflicts with Claude will attempt to resolve this automatically. If it cannot, a follow-up comment will explain what needs manual attention. To resolve manually instead: |
|
Auto-rebase failed — merge conflict — this branch has conflicts with Claude will attempt to resolve this automatically. If it cannot, a follow-up comment will explain what needs manual attention. To resolve manually instead: |
|
Auto-rebase failed — merge conflict — this branch has conflicts with Claude will attempt to resolve this automatically. If it cannot, a follow-up comment will explain what needs manual attention. To resolve manually instead: |
|
Auto-rebase failed — merge conflict — this branch has conflicts with Claude will attempt to resolve this automatically. If it cannot, a follow-up comment will explain what needs manual attention. To resolve manually instead: |
|
Auto-rebase failed — merge conflict — this branch has conflicts with Claude will attempt to resolve this automatically. If it cannot, a follow-up comment will explain what needs manual attention. To resolve manually instead: |
|
Auto-rebase failed — merge conflict — this branch has conflicts with Claude will attempt to resolve this automatically. If it cannot, a follow-up comment will explain what needs manual attention. To resolve manually instead: |
…iew retry loop The claude-opus-4-7 single-review confirmation could return valid JSON with an empty 'decision' field (e.g. when the model writes a partial JSON to $OUTPUT_FILE via Bash tool before completing). extract_verdict_json() only checked that the JSON was syntactically valid (jq empty), not that 'decision' was a recognized value. This caused review-batch.sh to pass the bad verdict to post-pr-review.sh which then hard-failed with 'ERROR: invalid decision ''' (exit code 1). Fix: after extract_verdict_json succeeds, check that decision is one of approve|escalate|skip before setting single_ok=true. If the value is anything else, fall through to the retry logic with a clear diagnostic message instead of crashing the entire batch. Reproducer: job/75706589647 — PR combsmith/.github#266 failed with 'ERROR: invalid decision ''' from post-pr-review.sh. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Auto-rebase failed — merge conflict — this branch has conflicts with Claude will attempt to resolve this automatically. If it cannot, a follow-up comment will explain what needs manual attention. To resolve manually instead: |
f24787a to
d50fc7a
Compare
|
No description provided. |
Automated review — human attention neededThis PR has been through 3 automated review cycles (cap: 3) without converging on an approval-and-merge state. Further automated review has been paused to avoid infinite loops. Please take a look manually, or close this PR if it's no longer needed. Once a human review resolves the situation, remove the Posted by the donpetry-bot PR-review cascade. |
|
No description provided. |
|
No description provided. |
|
No description provided. |
|
…iew retry loop (#153) The claude-opus-4-7 single-review confirmation could return valid JSON with an empty 'decision' field (e.g. when the model writes a partial JSON to $OUTPUT_FILE via Bash tool before completing). extract_verdict_json() only checked that the JSON was syntactically valid (jq empty), not that 'decision' was a recognized value. This caused review-batch.sh to pass the bad verdict to post-pr-review.sh which then hard-failed with 'ERROR: invalid decision ''' (exit code 1). Fix: after extract_verdict_json succeeds, check that decision is one of approve|escalate|skip before setting single_ok=true. If the value is anything else, fall through to the retry logic with a clear diagnostic message instead of crashing the entire batch. Reproducer: job/75706589647 — PR combsmith/.github#266 failed with 'ERROR: invalid decision ''' from post-pr-review.sh. Co-authored-by: don-petry <don@petry.dev> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…iew retry loop (#153) The claude-opus-4-7 single-review confirmation could return valid JSON with an empty 'decision' field (e.g. when the model writes a partial JSON to $OUTPUT_FILE via Bash tool before completing). extract_verdict_json() only checked that the JSON was syntactically valid (jq empty), not that 'decision' was a recognized value. This caused review-batch.sh to pass the bad verdict to post-pr-review.sh which then hard-failed with 'ERROR: invalid decision ''' (exit code 1). Fix: after extract_verdict_json succeeds, check that decision is one of approve|escalate|skip before setting single_ok=true. If the value is anything else, fall through to the retry logic with a clear diagnostic message instead of crashing the entire batch. Reproducer: job/75706589647 — PR combsmith/.github#266 failed with 'ERROR: invalid decision ''' from post-pr-review.sh. Co-authored-by: don-petry <don@petry.dev> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…iew retry loop (#153) The claude-opus-4-7 single-review confirmation could return valid JSON with an empty 'decision' field (e.g. when the model writes a partial JSON to $OUTPUT_FILE via Bash tool before completing). extract_verdict_json() only checked that the JSON was syntactically valid (jq empty), not that 'decision' was a recognized value. This caused review-batch.sh to pass the bad verdict to post-pr-review.sh which then hard-failed with 'ERROR: invalid decision ''' (exit code 1). Fix: after extract_verdict_json succeeds, check that decision is one of approve|escalate|skip before setting single_ok=true. If the value is anything else, fall through to the retry logic with a clear diagnostic message instead of crashing the entire batch. Reproducer: job/75706589647 — PR combsmith/.github#266 failed with 'ERROR: invalid decision ''' from post-pr-review.sh. Co-authored-by: don-petry <don@petry.dev> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…iew retry loop (#153) The claude-opus-4-7 single-review confirmation could return valid JSON with an empty 'decision' field (e.g. when the model writes a partial JSON to $OUTPUT_FILE via Bash tool before completing). extract_verdict_json() only checked that the JSON was syntactically valid (jq empty), not that 'decision' was a recognized value. This caused review-batch.sh to pass the bad verdict to post-pr-review.sh which then hard-failed with 'ERROR: invalid decision ''' (exit code 1). Fix: after extract_verdict_json succeeds, check that decision is one of approve|escalate|skip before setting single_ok=true. If the value is anything else, fall through to the retry logic with a clear diagnostic message instead of crashing the entire batch. Reproducer: job/75706589647 — PR combsmith/.github#266 failed with 'ERROR: invalid decision ''' from post-pr-review.sh. Co-authored-by: don-petry <don@petry.dev> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>



Problem
Job 75706589647 failed reviewing petry-projects/.github#266 with:
Root cause
The
claude-opus-4-7single-review confirmation ran successfully andextract_verdict_jsonextracted valid JSON — but the JSON had"decision": ""(empty string). This happens when the model writes a partial/placeholder JSON object to$OUTPUT_FILEvia a Bash tool call before it has finished reasoning, andextract_verdict_jsonStyle 1 accepts it (only checksjq empty— syntactic validity, not semantic content).The empty decision then propagated to
post-pr-review.shwhich correctly rejects it with exit code 1, crashing the whole batch.Fix
After
extract_verdict_jsonsucceeds, validate that.decisionis one ofapprove | escalate | skipbefore settingsingle_ok=true. If it is not, fall through to the existing retry logic with a clear diagnostic message. This turns a hard crash into a graceful retry (up to$SINGLE_REVIEW_MAX_RETRIESattempts), matching the behavior for malformed JSON.Change
scripts/review-one-pr.sh— single-review retry loop (lines 344–356)Summary by CodeRabbit