Skip to content

Commit ef1d525

Browse files
authored
Refactor: Add Optimization and better error handling (#26)
1 parent a703ec6 commit ef1d525

File tree

4 files changed

+71
-40
lines changed

4 files changed

+71
-40
lines changed

.github/workflows/code-review.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ on:
1414
jobs:
1515
code-review:
1616
runs-on: ubuntu-24.04
17+
if: github.event_name == 'pull_request' || (github.event_name == 'issue_comment' && github.event.issue.pull_request)
1718
permissions:
1819
contents: read
1920
pull-requests: write

README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,16 @@ on:
3434
jobs:
3535
review:
3636
runs-on: ubuntu-latest
37+
# IMPORTANT: This condition prevents the job from running on non-PR events,
38+
# saving GitHub Actions minutes. Without this, the runner starts and bills
39+
# for ~20-40 seconds even when the action skips internally.
40+
if: github.event_name == 'pull_request' || (github.event_name == 'issue_comment' && github.event.issue.pull_request)
3741
steps:
3842
- uses: actions/checkout@v4
3943
with:
4044
ref: ${{ github.event.pull_request.head.sha || github.sha }}
4145
fetch-depth: 2
42-
46+
4347
- uses: PSPDFKit-labs/nutrient-code-review@main
4448
with:
4549
comment-pr: true
@@ -176,8 +180,12 @@ on:
176180
jobs:
177181
review:
178182
runs-on: ubuntu-latest
183+
if: github.event_name == 'pull_request' || (github.event_name == 'issue_comment' && github.event.issue.pull_request)
179184
steps:
180185
- uses: actions/checkout@v4
186+
with:
187+
ref: ${{ github.event.pull_request.head.sha || github.sha }}
188+
fetch-depth: 2
181189
- uses: PSPDFKit-labs/nutrient-code-review@main
182190
with:
183191
claude-api-key: ${{ secrets.CLAUDE_API_KEY }}
@@ -202,6 +210,25 @@ concurrency:
202210
cancel-in-progress: false # Queues new runs instead
203211
```
204212

213+
### Billing Optimization (Recommended)
214+
215+
**Important:** Always add a job-level `if:` condition to prevent wasting GitHub Actions minutes on non-PR events.
216+
217+
Without this condition, the runner starts and bills for ~20-40 seconds even when the action skips internally. The `if:` condition prevents the job from starting at all:
218+
219+
```yaml
220+
jobs:
221+
review:
222+
runs-on: ubuntu-latest
223+
if: github.event_name == 'pull_request' || (github.event_name == 'issue_comment' && github.event.issue.pull_request)
224+
```
225+
226+
**Why this matters:**
227+
- Without job-level `if:`: Runner starts → action installs dependencies → determines to skip → **~20-40s billed**
228+
- With job-level `if:`: Job never starts for non-PR events → **0s billed**
229+
230+
This is especially important if you use `workflow_dispatch` or other event types that might not be PR-related.
231+
205232
## How It Works
206233

207234
### Architecture

action.yml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,7 @@ runs:
118118
# Install GitHub CLI for PR operations
119119
sudo apt-get update && sudo apt-get install -y gh
120120
echo "::endgroup::"
121-
122-
- name: Set up Python
123-
uses: actions/setup-python@v5
124-
with:
125-
python-version: '3.x'
126-
121+
127122
- name: Get PR info for issue_comment events
128123
id: pr-info
129124
if: github.event_name == 'issue_comment'
@@ -349,23 +344,28 @@ runs:
349344
with:
350345
path: .claudecode-marker
351346
key: claudecode-${{ github.repository_id }}-pr-${{ github.event.pull_request.number || steps.pr-info.outputs.pr_number }}-${{ github.event.pull_request.head.sha || steps.pr-info.outputs.pr_sha || github.sha }}
352-
347+
348+
- name: Set up Python
349+
if: steps.claudecode-check.outputs.enable_claudecode == 'true'
350+
uses: actions/setup-python@v5
351+
with:
352+
python-version: '3.x'
353+
353354
- name: Set up Node.js
354355
if: steps.claudecode-check.outputs.enable_claudecode == 'true'
355356
uses: actions/setup-node@v4
356357
with:
357358
node-version: '18'
358359

359360
- name: Install dependencies
361+
if: steps.claudecode-check.outputs.enable_claudecode == 'true'
360362
shell: bash
361363
env:
362364
ACTION_PATH: ${{ github.action_path }}
363365
run: |
364366
echo "::group::Install Deps"
365-
if [ "${{ steps.claudecode-check.outputs.enable_claudecode }}" == "true" ]; then
366-
pip install -r "$ACTION_PATH/claudecode/requirements.txt"
367-
npm install -g @anthropic-ai/claude-code
368-
fi
367+
pip install -r "$ACTION_PATH/claudecode/requirements.txt"
368+
npm install -g @anthropic-ai/claude-code
369369
sudo apt-get update && sudo apt-get install -y jq
370370
echo "::endgroup::"
371371

claudecode/github_action_audit.py

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -395,45 +395,48 @@ def run_code_review(self, repo_dir: Path, prompt: str) -> Tuple[bool, str, Dict[
395395
text=True,
396396
timeout=self.timeout_seconds
397397
)
398-
399-
if result.returncode != 0:
400-
if attempt == NUM_RETRIES - 1:
401-
error_details = f"Claude Code execution failed with return code {result.returncode}\n"
402-
error_details += f"Stderr: {result.stderr}\n"
403-
error_details += f"Stdout: {result.stdout[:500]}..." # First 500 chars
404-
return False, error_details, {}
405-
else:
406-
time.sleep(5*attempt)
407-
# Note: We don't do exponential backoff here to keep the runtime reasonable
408-
continue # Retry
409-
410-
# Parse JSON output
398+
399+
# Parse JSON output (even if returncode != 0, to detect specific errors)
411400
success, parsed_result = parse_json_with_fallbacks(result.stdout, "Claude Code output")
412-
401+
413402
if success:
414-
# Check for "Prompt is too long" error that should trigger retry without diff
415-
if (isinstance(parsed_result, dict) and
416-
parsed_result.get('type') == 'result' and
403+
# Check for "Prompt is too long" error that should trigger fallback to agentic mode
404+
if (isinstance(parsed_result, dict) and
405+
parsed_result.get('type') == 'result' and
417406
parsed_result.get('subtype') == 'success' and
418407
parsed_result.get('is_error') and
419408
parsed_result.get('result') == 'Prompt is too long'):
420409
return False, "PROMPT_TOO_LONG", {}
421-
410+
422411
# Check for error_during_execution that should trigger retry
423-
if (isinstance(parsed_result, dict) and
424-
parsed_result.get('type') == 'result' and
412+
if (isinstance(parsed_result, dict) and
413+
parsed_result.get('type') == 'result' and
425414
parsed_result.get('subtype') == 'error_during_execution' and
426415
attempt == 0):
427416
continue # Retry
428-
429-
# Extract review findings
430-
parsed_results = self._extract_review_findings(parsed_result)
431-
return True, "", parsed_results
432-
else:
433-
if attempt == 0:
434-
continue # Retry once
417+
418+
# If returncode is 0, extract review findings
419+
if result.returncode == 0:
420+
parsed_results = self._extract_review_findings(parsed_result)
421+
return True, "", parsed_results
422+
423+
# Handle non-zero return codes after parsing
424+
if result.returncode != 0:
425+
if attempt == NUM_RETRIES - 1:
426+
error_details = f"Claude Code execution failed with return code {result.returncode}\n"
427+
error_details += f"Stderr: {result.stderr}\n"
428+
error_details += f"Stdout: {result.stdout[:500]}..." # First 500 chars
429+
return False, error_details, {}
435430
else:
436-
return False, "Failed to parse Claude output", {}
431+
time.sleep(5*attempt)
432+
# Note: We don't do exponential backoff here to keep the runtime reasonable
433+
continue # Retry
434+
435+
# Parse failed
436+
if attempt == 0:
437+
continue # Retry once
438+
else:
439+
return False, "Failed to parse Claude output", {}
437440

438441
return False, "Unexpected error in retry logic", {}
439442

0 commit comments

Comments
 (0)