-
Notifications
You must be signed in to change notification settings - Fork 69
pr pre-flight, github action version #5558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mattwalsh
wants to merge
7
commits into
main
Choose a base branch
from
mattw/pr_preflight_action
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 4 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
1a50f38
pr pre-flight, github action version
mattwalsh 37a731c
greptile fixes
mattwalsh 22cb219
greptile says: better SHA data in env
mattwalsh 3644583
points for style
mattwalsh 71b2dc7
update secrets
xwang233 02460be
post comment there
xwang233 4137af0
fix env
xwang233 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2023-present NVIDIA CORPORATION & AFFILIATES. | ||
| # All rights reserved. | ||
| # SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| name: Claude CLI PR Review | ||
| on: | ||
| pull_request: | ||
| types: [opened, synchronize, ready_for_review] | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.event.pull_request.number }} | ||
| cancel-in-progress: true | ||
|
|
||
| run-name: Claude review for PR ${{ github.event.pull_request.number }} - ${{ github.event.pull_request.head.sha }} | ||
|
|
||
| jobs: | ||
| claude-code-review: | ||
| name: Run Claude Code Review | ||
| # Skip if PR is in draft | ||
| if: github.event.pull_request.draft == false | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| pull-requests: write | ||
| contents: read | ||
| env: | ||
| CLAUDE_OUTPUT_DIR: artifacts/claude_review/${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} | ||
|
|
||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '20' | ||
|
|
||
| - name: Set up Python | ||
| uses: actions/setup-python@v4 | ||
| with: | ||
| python-version: '3.10' | ||
|
|
||
| - name: Install Claude Code | ||
| run: | | ||
| npm install -g @anthropic-ai/claude-code | ||
| echo "$(npm config get prefix)/bin" >> $GITHUB_PATH | ||
|
|
||
| - name: Install Claude Code Router | ||
| run: npm install -g @musistudio/[email protected] | ||
|
|
||
| - name: Setup Claude Code Router config | ||
| run: | | ||
| mkdir -p $HOME/.claude-code-router | ||
| cat <<EOF > $HOME/.claude-code-router/config.json | ||
| { | ||
| "LOG": true, | ||
| "API_TIMEOUT_MS": 60000, | ||
| "NON_INTERACTIVE_MODE": true, | ||
| "Providers": [ | ||
| { | ||
| "name": "anthropic", | ||
| "api_base_url": "\$ANTHROPIC_BASE_URL", | ||
| "api_key": "\$ANTHROPIC_API_KEY", | ||
| "models": [ | ||
| "\$ANTHROPIC_LLM_MODEL" | ||
| ], | ||
| "transformer": { | ||
| "use": ["anthropic", "proxy-handler"] | ||
| } | ||
| } | ||
| ], | ||
| "Router": { | ||
| "default": "anthropic,\$ANTHROPIC_LLM_MODEL" | ||
| }, | ||
| "transformers": [ | ||
| { | ||
| "path": "$GITHUB_WORKSPACE/tools/proxy.js" | ||
| } | ||
| ] | ||
| } | ||
| EOF | ||
| shell: bash | ||
xwang233 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| - name: Start Claude Code Router background service | ||
| env: | ||
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | ||
| ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }} | ||
| ANTHROPIC_LLM_MODEL: ${{ secrets.ANTHROPIC_LLM_MODEL }} | ||
| run: | | ||
| nohup ccr start & | ||
| sleep 5 # Give it some time to start | ||
| shell: bash | ||
xwang233 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| - name: Check Claude CLI availability | ||
| run: | | ||
| echo "PATH=$PATH" | ||
| which claude || echo "claude not found on PATH" | ||
| claude --version || true | ||
| echo "npm prefix bin: $(npm config get prefix)/bin" || true | ||
|
|
||
| - name: Run Claude Code via wrapper | ||
| env: | ||
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | ||
| ANTHROPIC_BASE_URL: http://localhost:3456 | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| PR_SHA: ${{ github.event.pull_request.head.sha }} | ||
| PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} | ||
| PR_BASE_REF: ${{ github.event.pull_request.base.ref }} | ||
| PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} | ||
xwang233 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| PR_HEAD_REF: ${{ github.event.pull_request.head.ref }} | ||
| run: | | ||
| python -m tools.pr_preflight_launcher --ai-backend claude --output-dir "${CLAUDE_OUTPUT_DIR}" | ||
|
|
||
| - name: Print Claude error (if any) | ||
| if: always() | ||
| run: | | ||
| if [ -f "${{ env.CLAUDE_OUTPUT_DIR }}/error.txt" ]; then | ||
| echo "===== Claude error.txt =====" | ||
| sed -n '1,200p' "${{ env.CLAUDE_OUTPUT_DIR }}/error.txt" | ||
| else | ||
| echo "No error.txt found in ${{ env.CLAUDE_OUTPUT_DIR }}" | ||
| fi | ||
|
|
||
| - name: Upload Claude review artifacts | ||
| if: always() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: claude-review-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} | ||
| path: ${{ env.CLAUDE_OUTPUT_DIR }}/** | ||
|
|
||
| # TODO: Add step to post results to PR | ||
| # - name: Post results to PR | ||
| # run: | | ||
| # # Parse output and post to PR | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| name: Gemini CLI PR Review | ||
| on: | ||
| pull_request: | ||
| types: [opened, synchronize, ready_for_review] | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.event.pull_request.number }} | ||
| cancel-in-progress: true | ||
|
|
||
| run-name: Gemini review for PR ${{ github.event.pull_request.number }} - ${{ github.event.pull_request.head.sha }} | ||
|
|
||
| jobs: | ||
| gemini-pr-review: | ||
| # Skip if PR is in draft | ||
| if: github.event.pull_request.draft == false | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '20' | ||
|
|
||
| - name: Install Gemini CLI | ||
| shell: bash | ||
| run: | | ||
| npm install -g @google/gemini-cli@latest | ||
| echo "$(npm config get prefix)/bin" >> $GITHUB_PATH | ||
|
|
||
| - name: Verify Gemini CLI | ||
| shell: bash | ||
| run: | | ||
| which gemini | ||
| gemini --version | ||
|
|
||
| - name: Set up Python | ||
| uses: actions/setup-python@v4 | ||
| with: | ||
| python-version: '3.10' | ||
|
|
||
| - name: Install/Upgrade Google Python Client | ||
| shell: bash | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install --upgrade google-generativeai | ||
| # If you have a requirements.txt file, you might use this instead: | ||
| # pip install -r requirements.txt --upgrade | ||
|
|
||
| - name: Run Gemini PR review | ||
| env: | ||
| #GEMINI_MODEL: gemini-1.5-flash-latest | ||
| GEMINI_OUTPUT_DIR: artifacts/gemini_review/${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} | ||
| GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| PR_SHA: ${{ github.event.pull_request.head.sha }} | ||
| PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} | ||
| PR_BASE_REF: ${{ github.event.pull_request.base.ref }} | ||
| PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} | ||
xwang233 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| PR_HEAD_REF: ${{ github.event.pull_request.head.ref }} | ||
| run: | | ||
| python -m tools.pr_preflight_launcher --ai-backend gemini --output-dir "${GEMINI_OUTPUT_DIR}" | ||
|
|
||
| - name: Print Gemini error (if any) | ||
| if: always() | ||
| run: | | ||
| if [ -f "${{ env.GEMINI_OUTPUT_DIR }}/error.txt" ]; then | ||
| echo "===== Gemini error.txt =====" | ||
| sed -n '1,200p' "${{ env.GEMINI_OUTPUT_DIR }}/error.txt" | ||
| else | ||
| echo "No error.txt found in ${{ env.GEMINI_OUTPUT_DIR }}" | ||
| fi | ||
|
|
||
| - name: Upload Gemini review artifacts | ||
| if: always() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: gemini-review-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} | ||
| path: ${{ env.GEMINI_OUTPUT_DIR }}/** | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "action": "opened", | ||
| "pull_request": { | ||
| "draft": false, | ||
| "number": 5489, | ||
| "head": { "sha": "9f9a6b2f91519b3dc02fe7ec7a5f2a3b98398338", "ref": "feature/branch" }, | ||
| "base": { "sha": "f8b8551a720cd5c3a9aa8950e5a50fb7d420cbe5", "ref": "main" } | ||
| } | ||
| } | ||
|
|
||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # start from the official, clean Node.js 20 image (Debian-based) | ||
| # Gemini seems to go haywire with older node versions (e.g. 18) | ||
| FROM node:20 | ||
|
|
||
| COPY pr_preflight_launcher.py /usr/local/bin/ | ||
| COPY utils.py /usr/local/bin/tools/ | ||
| COPY git_helpers.py /usr/local/bin/tools/ | ||
| COPY ai_cli_wrapper.py /usr/local/bin/tools/ | ||
mattwalsh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # Add Python 3 and vim | ||
| RUN apt-get update && apt-get install -y python3 python3-pip vim | ||
|
|
||
| # Install the latest Gemini CLI globally inside the container | ||
| RUN npm install -g @google/gemini-cli --no-update-notifier | ||
|
|
||
| RUN node -v | ||
| RUN python3 --version | ||
| RUN gemini --version | ||
|
|
||
| # 5. Set a working directory (good practice) | ||
| WORKDIR /app | ||
|
|
||
| # 6. Set the default command. When the container starts, | ||
| # it will just run "bash", giving you an interactive shell. | ||
| CMD ["/bin/bash"] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| #!/usr/bin/env bash | ||
|
|
||
| # run me from the 'ai_cli_docker_test' directory | ||
|
|
||
| (cd .. && docker build -f ai_cli_docker_test/Dockerfile -t ai_cli_docker_test_shell .) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # once inside, can test with | ||
| # python3 /usr/local/bin/pr_preflight_launcher.py --output-dir=/tmp --base-sha=f8b8551a720cd5c3a9aa8950e5a50fb7d420cbe5 --head-sha=9f9a6b2f91519b3dc02fe7ec7a5f2a3b98398338 | ||
|
|
||
| # this assumes you have 'Fuser" at ~/dev/Fuser, which it will mount into /root/Fuser so it can | ||
| # do some git work | ||
| docker run -it -v ~/dev/Fuser:/root/Fuser -e GEMINI_API_KEY="$GEMINI_API_KEY" --rm ai_cli_docker_test_shell |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| Core Wrapper script for running Gemini or Claude CLI in CI. | ||
| Accepts a prompt string, expected verdict marker, and output directory path. | ||
|
|
||
| Exit codes: | ||
| 0 - Success (CLI ran, verdict was PASSED) | ||
| 1 - Network/timeout errors, 'gemini' command not found, or unexpected error | ||
| 2 - API errors (rate limits, etc.) [best-effort; CLI may not distinguish] | ||
| 3 - Review Failed (CLI ran, verdict was FAILED) | ||
| 4 - Parsing Error (CLI ran, but the required verdict marker was missing or ambiguous) | ||
| """ | ||
|
|
||
| import subprocess | ||
| from pathlib import Path | ||
| from tools.utils import ensure_dir, write_to_path | ||
|
|
||
|
|
||
| def launch_ai_cli( | ||
| prompt: str, | ||
| tool: str | None, | ||
| tool_args : list[str], | ||
| verdict_marker: str, | ||
| output_dir: Path | str, | ||
| timeout_seconds: int = 180 | ||
| ) -> int: | ||
|
|
||
|
|
||
| """ | ||
| Run Gemini / Claude CLI with the given prompt, check for verdict, and write outputs. | ||
| Returns an exit code: 0 (success), 3 (review failed), 4 (parsing error), 1/2 (errors). | ||
| """ | ||
| OUTPUT_DIR = Path(output_dir) | ||
| VERDICT_MARKER = verdict_marker.strip().upper() | ||
| ensure_dir(OUTPUT_DIR) | ||
|
|
||
| if tool is None: | ||
| write_to_path(OUTPUT_DIR, "error.txt", f"Error (Exit 1 - no tool specified)") | ||
| return 1 | ||
|
|
||
| try: | ||
| # Invoke CLI; pass prompt as a single argument | ||
| safety_instructions = ( | ||
| "CRITICAL RULE: If a tool execution fails, " | ||
| "OR if I have exceeded my API quote, DO NOT retry. " | ||
| "Stop immediately and report the error." | ||
| ) | ||
|
|
||
| prompt = f"{safety_instructions}\n\n{prompt}" | ||
|
|
||
| result = subprocess.run( | ||
| [tool] + tool_args + [prompt], | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=timeout_seconds, | ||
| check=False, | ||
| ) | ||
|
|
||
| # Combine stdout and stderr for complete output | ||
| full_output = result.stdout | ||
| if result.stderr: | ||
| full_output += f"\n\n--- STDERR ---\n{result.stderr}" | ||
|
|
||
| # --- Phase 1: Check for Tool/API Failure (Exit Codes 1 or 2) --- | ||
| if result.returncode != 0: | ||
| error_text = (result.stderr or result.stdout or "").lower() | ||
| # Try to detect API-ish errors | ||
| if any(phrase in error_text for phrase in ["rate limit", "too many requests", "quota", "api error", "unauthorized", "permission"]): | ||
| write_to_path(OUTPUT_DIR, "error.txt", f"API Error (Exit 2):\n{full_output}") | ||
| return 2 | ||
|
|
||
| # All other subprocess errors (Exit 1) | ||
| write_to_path(OUTPUT_DIR, "error.txt", f"Error (Exit 1 - Subprocess failed with code {result.returncode}):\n{full_output}") | ||
| return 1 | ||
|
|
||
| # --- Phase 2: Tool Success - Analyze Output (Exit Codes 0, 3, or 4) --- | ||
| write_to_path(OUTPUT_DIR, "success_raw_output.txt", full_output) | ||
|
|
||
| # Look for the verdict marker | ||
| verdict_line = next( | ||
| (line.strip().upper() for line in full_output.splitlines() if line.strip().upper().startswith(VERDICT_MARKER)), | ||
| None | ||
| ) | ||
|
|
||
| if verdict_line is None: | ||
| # Verdict marker not found | ||
| write_to_path(OUTPUT_DIR, "error.txt", f"Parsing Error (Exit 4): Verdict marker '{VERDICT_MARKER}' not found in output.") | ||
| return 4 | ||
|
|
||
| # Check the verdict | ||
| if "PASSED" in verdict_line: | ||
| write_to_path(OUTPUT_DIR, "review_verdict.txt", "VERDICT: PASSED") | ||
| return 0 | ||
| elif "FAILED" in verdict_line: | ||
| write_to_path(OUTPUT_DIR, "review_verdict.txt", "VERDICT: FAILED") | ||
| return 3 | ||
| else: | ||
| # Marker found, but value is ambiguous | ||
| write_to_path(OUTPUT_DIR, "error.txt", f"Parsing Error (Exit 4): Found verdict line but value is ambiguous: {verdict_line}") | ||
| return 4 | ||
|
|
||
| except subprocess.TimeoutExpired: | ||
| error_msg = f"{tool} command timed out after {timeout_seconds} seconds" | ||
| write_to_path(OUTPUT_DIR, "error.txt", error_msg) | ||
| return 1 | ||
|
|
||
| except FileNotFoundError: | ||
| error_msg = f"Error: '{tool}' command not found. Is it installed and in PATH?" | ||
| write_to_path(OUTPUT_DIR, "error.txt", error_msg) | ||
| return 1 | ||
|
|
||
| except Exception as e: | ||
| error_msg = f"Unexpected error: {type(e).__name__}: {str(e)}" | ||
| write_to_path(OUTPUT_DIR, "error.txt", error_msg) | ||
| return 1 |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: missing
fetch-depth: 0- without full git history,git merge-baseinpr_preflight_launcher.py:85will fail when computing the merge base between branches