Skip to content

feat: add should_complete to CompletionConfig #73

feat: add should_complete to CompletionConfig

feat: add should_complete to CompletionConfig #73

Workflow file for this run

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"