Skip to content

fix: make async row groups lazy #232

fix: make async row groups lazy

fix: make async row groups lazy #232

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
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 }}
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
review:
needs: gate
if: needs.gate.outputs.allowed == 'true'
runs-on: [self-hosted, agentic-ci]
environment: agentic-ci
timeout-minutes: 15
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
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
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 || true
continue-on-error: true
- name: Post review comment
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: 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"