ci: reuse OpenTelemetry conformance workflow #74
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: AI PR Review | |
| on: | |
| pull_request_target: | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| permissions: {} | |
| concurrency: | |
| group: ai-pr-review-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| jobs: | |
| approve_review: | |
| name: Approve AI PR review | |
| if: >- | |
| github.event.pull_request.user.login == 'dependabot[bot]' || | |
| github.event.pull_request.draft || | |
| github.event.pull_request.head.repo.full_name != github.repository | |
| runs-on: ubuntu-latest | |
| environment: ai-pr-review | |
| permissions: {} | |
| steps: | |
| - name: Record approval | |
| run: echo "Approved review of ${{ github.event.pull_request.head.sha }}" | |
| claude-review: | |
| name: Claude review | |
| needs: approve_review | |
| if: >- | |
| always() && | |
| ( | |
| ( | |
| github.event.pull_request.user.login != 'dependabot[bot]' && | |
| !github.event.pull_request.draft && | |
| github.event.pull_request.head.repo.full_name == github.repository | |
| ) || | |
| needs.approve_review.result == 'success' | |
| ) | |
| runs-on: ubuntu-latest | |
| environment: ai-pr-review-runtime | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: read | |
| id-token: write | |
| pull-requests: write | |
| steps: | |
| # pull_request_target exposes repository secrets, so only check out the | |
| # exact trusted base revision. The PR's code is never checked out. | |
| - name: Check out base branch | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 1 | |
| persist-credentials: false | |
| ref: ${{ github.event.pull_request.base.sha }} | |
| - name: Prepare pull request review context | |
| env: | |
| EXPECTED_BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: bash scripts/prepare_ai_review_context.sh | |
| - name: Prepare shared review inputs | |
| id: review-inputs | |
| run: | | |
| inline_marker="<!-- ai-pr-review:inline:claude:${GITHUB_RUN_ID}:${GITHUB_RUN_ATTEMPT}:primary -->" | |
| retry_inline_marker="<!-- ai-pr-review:inline:claude:${GITHUB_RUN_ID}:${GITHUB_RUN_ATTEMPT}:retry -->" | |
| { | |
| echo 'prompt<<AI_REVIEW_PROMPT' | |
| cat .github/prompts/ai-pr-review.md | |
| printf '\nFor every inline comment, the first line of the comment body must be exactly:\n%s\nPut the finding text on the following lines. Do not include this marker in the final response.\n' \ | |
| "$inline_marker" | |
| echo 'AI_REVIEW_PROMPT' | |
| echo 'retry_prompt<<AI_REVIEW_RETRY_PROMPT' | |
| cat .github/prompts/ai-pr-review.md | |
| printf '\nFor every inline comment, the first line of the comment body must be exactly:\n%s\nPut the finding text on the following lines. Do not include this marker in the final response.\n' \ | |
| "$retry_inline_marker" | |
| echo 'AI_REVIEW_RETRY_PROMPT' | |
| echo "schema=$(jq -c . .github/prompts/ai-pr-review-schema.json)" | |
| echo "inline_marker=$inline_marker" | |
| echo "retry_inline_marker=$retry_inline_marker" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Prepare unprivileged Claude user | |
| run: | | |
| bash scripts/prepare_ai_review_user.sh claude-review | |
| # The Claude CLI runs through sudo. Preserve only its Bedrock | |
| # credentials and the action's subprocess-isolation controls. | |
| sudo sh -c \ | |
| 'printf "%s\n" \ | |
| "Defaults:runner env_keep += \"AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_REGION AWS_DEFAULT_REGION AWS_BEARER_TOKEN_BEDROCK\"" \ | |
| "Defaults:runner env_keep += \"ANTHROPIC_BEDROCK_BASE_URL CLAUDE_CODE_USE_BEDROCK CLAUDE_CODE_ENTRYPOINT CLAUDE_CODE_ACTION CLAUDE_CODE_ATTRIBUTION_HEADER\"" \ | |
| "Defaults:runner env_keep += \"CLAUDE_CODE_SUBPROCESS_ENV_SCRUB CLAUDE_CODE_SCRIPT_CAPS DETAILED_PERMISSION_MESSAGES MCP_TIMEOUT MCP_TOOL_TIMEOUT MAX_MCP_OUTPUT_TOKENS\"" \ | |
| > /etc/sudoers.d/claude-review-env' | |
| sudo chmod 440 /etc/sudoers.d/claude-review-env | |
| sudo visudo -cf /etc/sudoers.d/claude-review-env | |
| - name: Configure AWS credentials for Claude | |
| uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2 | |
| with: | |
| role-to-assume: ${{ secrets.BEDROCK_ROLE_ARN }} | |
| role-session-name: claude-pr-review-${{ github.run_id }} | |
| aws-region: us-east-1 | |
| inline-session-policy: | | |
| { | |
| "Version": "2012-10-17", | |
| "Statement": [{ | |
| "Effect": "Allow", | |
| "Action": [ | |
| "bedrock:InvokeModel", | |
| "bedrock:InvokeModelWithResponseStream" | |
| ], | |
| "Resource": "*" | |
| }] | |
| } | |
| mask-aws-account-id: true | |
| output-env-credentials: true | |
| - name: Review pull request with Claude on Amazon Bedrock | |
| id: review | |
| continue-on-error: true | |
| uses: anthropics/claude-code-action@fa7e2f0a29a126f0b81cdcf360561b36e44cf608 # v1.0.180 | |
| env: | |
| AWS_REGION: us-east-1 | |
| CLAUDE_CODE_ATTRIBUTION_HEADER: "0" | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| use_bedrock: "true" | |
| path_to_claude_code_executable: ${{ github.workspace }}/scripts/run_claude_isolated.sh | |
| # This also keeps the token out of the git remote configured by the action. | |
| allowed_non_write_users: ${{ github.actor }} | |
| classify_inline_comments: "false" | |
| prompt: ${{ steps.review-inputs.outputs.prompt }} | |
| claude_args: | | |
| --model us.anthropic.claude-opus-4-8 | |
| --max-turns 20 | |
| --allowedTools "Read,Grep,Glob,mcp__github_inline_comment__create_inline_comment" | |
| --disallowedTools "Bash,Write,Edit,NotebookEdit,WebFetch,WebSearch" | |
| --json-schema '${{ steps.review-inputs.outputs.schema }}' | |
| - name: Retry pull request review with Claude on Amazon Bedrock | |
| id: review-retry | |
| if: steps.review.outcome == 'failure' | |
| uses: anthropics/claude-code-action@fa7e2f0a29a126f0b81cdcf360561b36e44cf608 # v1.0.180 | |
| env: | |
| AWS_REGION: us-east-1 | |
| CLAUDE_CODE_ATTRIBUTION_HEADER: "0" | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| use_bedrock: "true" | |
| path_to_claude_code_executable: ${{ github.workspace }}/scripts/run_claude_isolated.sh | |
| # This also keeps the token out of the git remote configured by the action. | |
| allowed_non_write_users: ${{ github.actor }} | |
| classify_inline_comments: "false" | |
| prompt: ${{ steps.review-inputs.outputs.retry_prompt }} | |
| claude_args: | | |
| --model us.anthropic.claude-opus-4-8 | |
| --max-turns 20 | |
| --allowedTools "Read,Grep,Glob,mcp__github_inline_comment__create_inline_comment" | |
| --disallowedTools "Bash,Write,Edit,NotebookEdit,WebFetch,WebSearch" | |
| --json-schema '${{ steps.review-inputs.outputs.schema }}' | |
| - name: Post review summary | |
| env: | |
| EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| CURRENT_INLINE_COMMENT_MARKER: >- | |
| ${{ | |
| steps.review.outcome == 'success' && | |
| steps.review-inputs.outputs.inline_marker || | |
| steps.review-inputs.outputs.retry_inline_marker | |
| }} | |
| REVIEW_OUTPUT: >- | |
| ${{ | |
| steps.review.outcome == 'success' && | |
| steps.review.outputs.structured_output || | |
| steps.review-retry.outputs.structured_output | |
| }} | |
| run: | | |
| set -euo pipefail | |
| summary="$( | |
| jq -ser ' | |
| select(length == 1) | | |
| .[0] | | |
| select(type == "object" and keys == ["summary"]) | | |
| .summary | | |
| select( | |
| type == "string" and | |
| length <= 4000 and | |
| test("[^[:space:]]") | |
| ) | |
| ' <<< "$REVIEW_OUTPUT" | |
| )" | |
| summary="$( | |
| sed -E \ | |
| -e '1{/^## (Claude|Codex) AI review$/d;}' \ | |
| -e '/[^[:space:]]/,$!d' \ | |
| <<< "$summary" | |
| )" | |
| if [[ -z "${summary//[[:space:]]/}" ]]; then | |
| echo "::error::Claude returned an empty review body." | |
| exit 1 | |
| fi | |
| summary_file="${RUNNER_TEMP}/claude-review-summary.md" | |
| printf '%s' "$summary" > "$summary_file" | |
| bash scripts/post_ai_review_summary.sh \ | |
| claude \ | |
| "$EXPECTED_HEAD_SHA" \ | |
| "$summary_file" | |
| codex-review: | |
| name: Codex review | |
| needs: approve_review | |
| if: >- | |
| always() && | |
| ( | |
| ( | |
| github.event.pull_request.user.login != 'dependabot[bot]' && | |
| !github.event.pull_request.draft && | |
| github.event.pull_request.head.repo.full_name == github.repository | |
| ) || | |
| needs.approve_review.result == 'success' | |
| ) | |
| runs-on: ubuntu-latest | |
| environment: ai-pr-review-runtime | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: read | |
| id-token: write | |
| pull-requests: write | |
| steps: | |
| # pull_request_target exposes repository secrets, so only check out the | |
| # exact trusted base revision. The PR's code is never checked out. | |
| - name: Check out base branch | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 1 | |
| persist-credentials: false | |
| ref: ${{ github.event.pull_request.base.sha }} | |
| - name: Prepare pull request review context | |
| env: | |
| EXPECTED_BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: bash scripts/prepare_ai_review_context.sh | |
| - name: Prepare unprivileged Codex user | |
| run: | | |
| bash scripts/prepare_ai_review_user.sh codex-review | |
| # Codex runs through sudo, so preserve its AWS SDK credential chain. | |
| sudo sh -c \ | |
| 'printf "%s\n" \ | |
| "Defaults:runner env_keep += \"AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_REGION AWS_DEFAULT_REGION\"" \ | |
| > /etc/sudoers.d/codex-review-env' | |
| sudo chmod 440 /etc/sudoers.d/codex-review-env | |
| sudo visudo -cf /etc/sudoers.d/codex-review-env | |
| current_userns="$( | |
| sysctl -n kernel.unprivileged_userns_clone 2>/dev/null || true | |
| )" | |
| if [[ -n "$current_userns" && "$current_userns" != "1" ]]; then | |
| sudo sysctl -w kernel.unprivileged_userns_clone=1 | |
| fi | |
| current_apparmor="$( | |
| sysctl -n kernel.apparmor_restrict_unprivileged_userns \ | |
| 2>/dev/null || true | |
| )" | |
| if [[ -n "$current_apparmor" && "$current_apparmor" != "0" ]]; then | |
| sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 | |
| fi | |
| - name: Set up Node.js | |
| uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 | |
| with: | |
| node-version: "24" | |
| - name: Install Codex CLI | |
| run: npm install -g "@openai/codex@0.145.0" | |
| - name: Configure AWS credentials for Codex | |
| id: aws-credentials | |
| uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2 | |
| with: | |
| role-to-assume: ${{ secrets.BEDROCK_ROLE_ARN }} | |
| role-session-name: codex-pr-review-${{ github.run_id }} | |
| aws-region: us-east-1 | |
| inline-session-policy: | | |
| { | |
| "Version": "2012-10-17", | |
| "Statement": [{ | |
| "Effect": "Allow", | |
| "Action": [ | |
| "bedrock-mantle:CreateInference", | |
| "bedrock-mantle:GetProject", | |
| "bedrock-mantle:ListProjects", | |
| "bedrock-mantle:ListTagsForResources" | |
| ], | |
| "Resource": "*" | |
| }] | |
| } | |
| mask-aws-account-id: true | |
| output-credentials: true | |
| output-env-credentials: false | |
| - name: Review pull request with Codex on Amazon Bedrock | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ steps.aws-credentials.outputs.aws-access-key-id }} | |
| AWS_SECRET_ACCESS_KEY: ${{ steps.aws-credentials.outputs.aws-secret-access-key }} | |
| AWS_SESSION_TOKEN: ${{ steps.aws-credentials.outputs.aws-session-token }} | |
| AWS_REGION: us-east-1 | |
| AWS_DEFAULT_REGION: us-east-1 | |
| run: | | |
| set -euo pipefail | |
| codex_bin="$(command -v codex)" | |
| output_dir="$( | |
| sudo -u codex-review mktemp -d -t codex-review.XXXXXX | |
| )" | |
| output_file="${output_dir}/review.json" | |
| output_schema="${GITHUB_WORKSPACE}/.github/prompts/ai-pr-review-schema.json" | |
| trap 'sudo rm -rf "$output_dir"' EXIT | |
| sudo -u codex-review -- sh -c ' | |
| : "${AWS_ACCESS_KEY_ID:?AWS_ACCESS_KEY_ID was not preserved by sudo}" | |
| : "${AWS_SECRET_ACCESS_KEY:?AWS_SECRET_ACCESS_KEY was not preserved by sudo}" | |
| : "${AWS_SESSION_TOKEN:?AWS_SESSION_TOKEN was not preserved by sudo}" | |
| : "${AWS_REGION:?AWS_REGION was not preserved by sudo}" | |
| ' | |
| review_ready=false | |
| for attempt in 1 2; do | |
| sudo -u codex-review -- rm -f "$output_file" | |
| # Keep enough of the job budget for a complete second attempt. | |
| if sudo -u codex-review -- timeout \ | |
| --signal=TERM \ | |
| --kill-after=30s \ | |
| 20m \ | |
| env \ | |
| CODEX_HOME=/home/codex-review/.codex \ | |
| "$codex_bin" exec \ | |
| --skip-git-repo-check \ | |
| --cd "$GITHUB_WORKSPACE" \ | |
| --output-last-message "$output_file" \ | |
| --output-schema "$output_schema" \ | |
| --color never \ | |
| --model openai.gpt-5.6-sol \ | |
| --config 'model_reasoning_effort="xhigh"' \ | |
| --config 'model_provider="amazon-bedrock"' \ | |
| --config 'default_permissions=":read-only"' \ | |
| --ephemeral < .github/prompts/ai-pr-review.md | |
| then | |
| if sudo -u codex-review -- jq -se ' | |
| length == 1 and | |
| ( | |
| .[0] | | |
| type == "object" and | |
| keys == ["summary"] and | |
| ( | |
| .summary | | |
| type == "string" and | |
| length <= 4000 and | |
| test("[^[:space:]]") | |
| ) | |
| ) | |
| ' "$output_file" > /dev/null | |
| then | |
| review_ready=true | |
| break | |
| fi | |
| echo "::warning::Codex attempt ${attempt} returned invalid review output." | |
| else | |
| echo "::warning::Codex attempt ${attempt} failed." | |
| fi | |
| done | |
| if [[ "$review_ready" != "true" ]]; then | |
| echo "::error::Codex did not return a valid review after two attempts." | |
| exit 1 | |
| fi | |
| sudo install \ | |
| -m 600 \ | |
| -o runner \ | |
| -g runner \ | |
| "$output_file" \ | |
| "${RUNNER_TEMP}/codex-review.json" | |
| - name: Post Codex review | |
| env: | |
| CODEX_REVIEW_FILE: ${{ runner.temp }}/codex-review.json | |
| EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: | | |
| set -euo pipefail | |
| CODEX_REVIEW="$( | |
| jq -ser ' | |
| select(length == 1) | | |
| .[0] | | |
| select(type == "object" and keys == ["summary"]) | | |
| .summary | | |
| select( | |
| type == "string" and | |
| length <= 4000 and | |
| test("[^[:space:]]") | |
| ) | |
| ' "$CODEX_REVIEW_FILE" | |
| )" | |
| CODEX_REVIEW="$( | |
| sed -E \ | |
| -e '1{/^## (Claude|Codex) AI review$/d;}' \ | |
| -e '/[^[:space:]]/,$!d' \ | |
| <<< "$CODEX_REVIEW" | |
| )" | |
| if [[ -z "${CODEX_REVIEW//[[:space:]]/}" ]]; then | |
| echo "::error::Codex returned an empty review." | |
| exit 1 | |
| fi | |
| summary_file="${RUNNER_TEMP}/codex-review-summary.md" | |
| printf '%s' "$CODEX_REVIEW" > "$summary_file" | |
| bash scripts/post_ai_review_summary.sh \ | |
| codex \ | |
| "$EXPECTED_HEAD_SHA" \ | |
| "$summary_file" |