ci: bump the all-actions group with 2 updates #348
Workflow file for this run
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
| name: "Agentic CI: PR Review" | |
| on: | |
| pull_request_target: | |
| types: [opened, ready_for_review, labeled] | |
| branches: [main] | |
| workflow_dispatch: | |
| inputs: | |
| pr_number: | |
| description: "PR number to review" | |
| required: true | |
| timeout_minutes: | |
| description: "Review timeout in minutes. Defaults to AGENTIC_CI_TIMEOUT_MINUTES or 30." | |
| required: false | |
| permissions: | |
| checks: write | |
| contents: read | |
| pull-requests: write | |
| concurrency: | |
| group: agentic-ci-pr-review-${{ github.event.pull_request.number || github.event.inputs.pr_number }} | |
| cancel-in-progress: true | |
| jobs: | |
| gate: | |
| # Decide whether the review job should run. Uses the collaborator API | |
| # instead of author_association (which is unreliable when org membership | |
| # is private). | |
| runs-on: ubuntu-latest | |
| outputs: | |
| allowed: ${{ steps.check.outputs.allowed }} | |
| timeout_minutes: ${{ steps.timeout.outputs.minutes }} | |
| steps: | |
| - name: Check permissions | |
| id: check | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| EVENT_ACTION: ${{ github.event.action }} | |
| LABEL_NAME: ${{ github.event.label.name }} | |
| IS_DRAFT: ${{ github.event.pull_request.draft }} | |
| SENDER_LOGIN: ${{ github.event.sender.login }} | |
| PR_AUTHOR: ${{ github.event.pull_request.user.login }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| # workflow_dispatch callers already have write access. | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | |
| echo "allowed=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # Only the agent-review label should trigger a run. | |
| if [ "$EVENT_ACTION" = "labeled" ] && [ "$LABEL_NAME" != "agent-review" ]; then | |
| echo "Skipping: labeled event but not agent-review" | |
| echo "allowed=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # Skip drafts unless agent-review label is being added. | |
| if [ "$IS_DRAFT" = "true" ]; then | |
| if [ "$EVENT_ACTION" != "labeled" ] || [ "$LABEL_NAME" != "agent-review" ]; then | |
| echo "Skipping: draft PR" | |
| echo "allowed=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| fi | |
| # For labeled events, check the sender (who added the label) so | |
| # maintainers can authorize reviews on external PRs. | |
| # For other events, check the PR author. | |
| if [ "$EVENT_ACTION" = "labeled" ]; then | |
| USER="$SENDER_LOGIN" | |
| echo "Checking sender (labeler): ${USER}" | |
| else | |
| USER="$PR_AUTHOR" | |
| echo "Checking PR author: ${USER}" | |
| fi | |
| PERMISSION=$(gh api "repos/${REPO}/collaborators/${USER}/permission" --jq '.permission' 2>/dev/null || echo "none") | |
| echo "permission=${PERMISSION}" | |
| if [ "$PERMISSION" = "admin" ] || [ "$PERMISSION" = "write" ]; then | |
| echo "allowed=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Skipping: ${USER} does not have write access (permission=${PERMISSION})" | |
| echo "allowed=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Resolve review timeout | |
| id: timeout | |
| if: steps.check.outputs.allowed == 'true' | |
| env: | |
| INPUT_TIMEOUT_MINUTES: ${{ inputs.timeout_minutes }} | |
| CONFIG_TIMEOUT_MINUTES: ${{ vars.AGENTIC_CI_TIMEOUT_MINUTES }} | |
| run: | | |
| TIMEOUT_MINUTES="${INPUT_TIMEOUT_MINUTES:-${CONFIG_TIMEOUT_MINUTES:-30}}" | |
| if ! [[ "$TIMEOUT_MINUTES" =~ ^[0-9]+$ ]] || | |
| (( TIMEOUT_MINUTES < 1 || TIMEOUT_MINUTES > 45 )); then | |
| echo "::error::Review timeout must be an integer from 1 to 45 minutes." | |
| exit 1 | |
| fi | |
| echo "minutes=${TIMEOUT_MINUTES}" >> "$GITHUB_OUTPUT" | |
| review: | |
| name: Agentic review (advisory) | |
| needs: gate | |
| if: needs.gate.outputs.allowed == 'true' | |
| runs-on: [self-hosted, agentic-ci] | |
| environment: agentic-ci | |
| timeout-minutes: 60 | |
| steps: | |
| - name: Determine PR number | |
| id: pr | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| INPUT_PR_NUMBER: ${{ github.event.inputs.pr_number }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: | | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | |
| echo "number=${INPUT_PR_NUMBER}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "number=${PR_NUMBER}" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Validate PR number | |
| env: | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} | |
| run: | | |
| if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then | |
| echo "::error::Invalid PR number: ${PR_NUMBER}" | |
| exit 1 | |
| fi | |
| - name: Check required config | |
| env: | |
| AGENTIC_CI_MODEL: ${{ vars.AGENTIC_CI_MODEL }} | |
| run: | | |
| if [ -z "$AGENTIC_CI_MODEL" ]; then | |
| echo "::error::AGENTIC_CI_MODEL variable is not set. Configure it in repo settings." | |
| exit 1 | |
| fi | |
| - name: Resolve head SHA | |
| id: head | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} | |
| PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| run: | | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | |
| SHA=$(gh pr view "$PR_NUMBER" --json headRefOid -q '.headRefOid') | |
| else | |
| SHA="$PR_HEAD_SHA" | |
| fi | |
| echo "sha=$SHA" >> "$GITHUB_OUTPUT" | |
| - name: Checkout PR branch | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| # Preserve maintainer-approved fork reviews while #804 hardens isolation. | |
| allow-unsafe-pr-checkout: true | |
| ref: ${{ steps.head.outputs.sha }} | |
| fetch-depth: 0 | |
| # SECURITY: Recipe and tool files define the agent's prompt and run | |
| # with secrets in scope. Always read them from the base branch so a | |
| # fork PR cannot inject malicious instructions or code. | |
| - name: Checkout base branch agent files | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| ref: ${{ github.event.pull_request.base.sha || 'main' }} | |
| sparse-checkout: | | |
| .agents/recipes | |
| .agents/tools | |
| path: base-agents | |
| - name: List changed Python files | |
| id: changed-py | |
| env: | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| gh pr diff "$PR_NUMBER" --name-only | grep '\.py$' > /tmp/changed-py.txt || true | |
| echo "count=$(wc -l < /tmp/changed-py.txt | tr -d ' ')" >> "$GITHUB_OUTPUT" | |
| - name: Structural impact analysis | |
| if: steps.changed-py.outputs.count != '0' | |
| run: | | |
| rm -f "/tmp/structural-impact-${{ steps.pr.outputs.number }}.md" | |
| mapfile -t CHANGED_PY < /tmp/changed-py.txt | |
| python -m venv /tmp/graphify-venv | |
| /tmp/graphify-venv/bin/python -m pip install graphifyy==0.4.23 --quiet 2>&1 | tail -3 | |
| /tmp/graphify-venv/bin/python base-agents/.agents/tools/structural_impact.py \ | |
| --repo-root "${{ github.workspace }}" \ | |
| --changed-files "${CHANGED_PY[@]}" \ | |
| --output "/tmp/structural-impact-${{ steps.pr.outputs.number }}.md" | |
| echo "Structural impact analysis complete:" | |
| cat "/tmp/structural-impact-${{ steps.pr.outputs.number }}.md" | |
| continue-on-error: true | |
| - name: Pre-flight checks | |
| env: | |
| ANTHROPIC_BASE_URL: ${{ secrets.AGENTIC_CI_API_BASE_URL }} | |
| ANTHROPIC_API_KEY: ${{ secrets.AGENTIC_CI_API_KEY }} | |
| AGENTIC_CI_MODEL: ${{ vars.AGENTIC_CI_MODEL }} | |
| run: | | |
| if ! command -v claude &> /dev/null; then | |
| echo "::error::claude CLI not found in PATH" | |
| exit 1 | |
| fi | |
| echo "Claude CLI version: $(claude --version 2>&1 || true)" | |
| # Quick API check (custom endpoint only) | |
| if [ -n "$ANTHROPIC_BASE_URL" ] && [ -n "$ANTHROPIC_API_KEY" ]; then | |
| HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ | |
| --max-time 30 \ | |
| -X POST "${ANTHROPIC_BASE_URL}/v1/messages" \ | |
| -H "Content-Type: application/json" \ | |
| -H "x-api-key: ${ANTHROPIC_API_KEY}" \ | |
| -H "anthropic-version: 2023-06-01" \ | |
| -d "{\"model\":\"${AGENTIC_CI_MODEL}\",\"max_tokens\":5,\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}") | |
| if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then | |
| echo "::error::API pre-flight failed with HTTP ${HTTP_CODE}" | |
| exit 1 | |
| fi | |
| echo "API pre-flight passed (HTTP ${HTTP_CODE})" | |
| fi | |
| - name: Run PR review recipe | |
| id: review | |
| timeout-minutes: ${{ fromJSON(needs.gate.outputs.timeout_minutes) }} | |
| env: | |
| ANTHROPIC_BASE_URL: ${{ secrets.AGENTIC_CI_API_BASE_URL }} | |
| ANTHROPIC_API_KEY: ${{ secrets.AGENTIC_CI_API_KEY }} | |
| AGENTIC_CI_MODEL: ${{ vars.AGENTIC_CI_MODEL }} | |
| DISABLE_PROMPT_CACHING: "1" | |
| GH_TOKEN: ${{ github.token }} | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} | |
| run: | | |
| set -o pipefail | |
| # Build the prompt from _runner.md + recipe, substituting template vars. | |
| # Read from base-agents/ (checked out from the base branch) so fork | |
| # PRs cannot tamper with the agent prompt. | |
| RUNNER_CTX=$(cat base-agents/.agents/recipes/_runner.md) | |
| RECIPE_BODY=$(cat base-agents/.agents/recipes/pr-review/recipe.md \ | |
| | sed '1,/^---$/{ /^---$/,/^---$/d }') | |
| PROMPT=$(printf '%s\n\n%s\n' "${RUNNER_CTX}" "${RECIPE_BODY}" \ | |
| | sed "s/{{pr_number}}/${PR_NUMBER}/g") | |
| claude \ | |
| --model "$AGENTIC_CI_MODEL" \ | |
| -p "$PROMPT" \ | |
| --max-turns 30 \ | |
| --output-format text \ | |
| --verbose \ | |
| 2>&1 | tee /tmp/claude-review-log.txt | |
| continue-on-error: true | |
| - name: Report incomplete review | |
| if: steps.review.outcome != 'success' | |
| env: | |
| TIMEOUT_MINUTES: ${{ needs.gate.outputs.timeout_minutes }} | |
| run: | | |
| echo "::warning::Agentic review failed or exceeded the ${TIMEOUT_MINUTES}-minute limit." | |
| { | |
| echo "## Agentic review incomplete" | |
| echo | |
| echo "The advisory review failed or timed out. This does not block merging." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Post review comment | |
| if: steps.review.outcome == 'success' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} | |
| run: | | |
| if [ -s "/tmp/review-${PR_NUMBER}.md" ]; then | |
| gh pr comment "$PR_NUMBER" --body-file "/tmp/review-${PR_NUMBER}.md" | |
| else | |
| echo "::warning::Review file not created by agent." | |
| fi | |
| - name: Remove agent-review label | |
| if: always() && github.event.action == 'labeled' && github.event.label.name == 'agent-review' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} | |
| run: | | |
| gh pr edit "$PR_NUMBER" --remove-label "agent-review" |