Skip to content

Add fallback AI provider support to code review workflow #2

Add fallback AI provider support to code review workflow

Add fallback AI provider support to code review workflow #2

Workflow file for this run

name: AI Code Review
on:
pull_request:
types: [opened, synchronize]
workflow_dispatch:
inputs:
ai_provider:
description: 'Primary AI provider for this run'
required: false
default: 'claude'
type: choice
options:
- claude
- openai
jobs:
review:
runs-on: ubuntu-latest
permissions:
pull-requests: write
# Priority: manual dispatch input > repo variable (vars.AI_PROVIDER) > default 'claude'
env:
AI_PROVIDER: ${{ github.event_name == 'workflow_dispatch' && inputs.ai_provider || vars.AI_PROVIDER || 'claude' }}
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get PR diff
run: |
BASE_REF="${{ github.base_ref || 'main' }}"
git fetch origin "$BASE_REF"
git diff "origin/$BASE_REF" > diff.txt
- name: Build prompt
env:
PR_BODY: ${{ github.event.pull_request.body }}
run: |
printf '%s\n' "$PR_BODY" > pr_body.txt
python3 -c "
template = open('.github/prompt-template.txt').read()
rules = open('.github/review-guidelines.md').read()
pr_body = open('pr_body.txt').read()
diff = open('diff.txt').read()
open('prompt.txt', 'w').write(template.format(rules=rules, pr_body=pr_body, diff=diff))
"
# Tries the configured primary provider. continue-on-error allows the
# fallback step to run if this fails (bad key, quota, outage, etc.)
- name: Call primary provider
id: primary
continue-on-error: true
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
if [ "$AI_PROVIDER" = "claude" ]; then
PAYLOAD=$(jq -n --rawfile prompt prompt.txt \
'{model:"claude-sonnet-4-6",max_tokens:1024,messages:[{role:"user",content:$prompt}]}')
RESPONSE=$(curl -sf https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d "$PAYLOAD")
REVIEW=$(echo "$RESPONSE" | jq -re '.content[0].text')
USED="Claude Sonnet 4.6 (Anthropic)"
else
PAYLOAD=$(jq -n --rawfile prompt prompt.txt \
'{model:"gpt-4o-mini",temperature:0.3,messages:[{role:"user",content:$prompt}]}')
RESPONSE=$(curl -sf https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d "$PAYLOAD")
REVIEW=$(echo "$RESPONSE" | jq -re '.choices[0].message.content')
USED="GPT-4o-mini (OpenAI)"
fi
echo "REVIEW<<EOF" >> "$GITHUB_OUTPUT"
printf '%s\n' "$REVIEW" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
echo "USED=$USED" >> "$GITHUB_OUTPUT"
# Only runs if the primary provider failed — automatically uses the other provider.
- name: Call fallback provider
id: fallback
if: steps.primary.outcome == 'failure'
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
echo "::warning::Primary provider ($AI_PROVIDER) failed — switching to fallback."
if [ "$AI_PROVIDER" = "claude" ]; then
PAYLOAD=$(jq -n --rawfile prompt prompt.txt \
'{model:"gpt-4o-mini",temperature:0.3,messages:[{role:"user",content:$prompt}]}')
RESPONSE=$(curl -sf https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d "$PAYLOAD") || { echo "::error::Both Claude and OpenAI failed."; exit 1; }
REVIEW=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // ("OpenAI error: " + (.error.message // "unknown"))')
USED="GPT-4o-mini (OpenAI) ⚠️ fallback from Claude"
else
PAYLOAD=$(jq -n --rawfile prompt prompt.txt \
'{model:"claude-sonnet-4-6",max_tokens:1024,messages:[{role:"user",content:$prompt}]}')
RESPONSE=$(curl -sf https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d "$PAYLOAD") || { echo "::error::Both OpenAI and Claude failed."; exit 1; }
REVIEW=$(echo "$RESPONSE" | jq -r '.content[0].text // ("Claude error: " + (.error.message // "unknown"))')
USED="Claude Sonnet 4.6 (Anthropic) ⚠️ fallback from OpenAI"
fi
echo "REVIEW<<EOF" >> "$GITHUB_OUTPUT"
printf '%s\n' "$REVIEW" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
echo "USED=$USED" >> "$GITHUB_OUTPUT"
- name: Post review comment
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
env:
PRIMARY_REVIEW: ${{ steps.primary.outputs.REVIEW }}
PRIMARY_USED: ${{ steps.primary.outputs.USED }}
FALLBACK_REVIEW: ${{ steps.fallback.outputs.REVIEW }}
FALLBACK_USED: ${{ steps.fallback.outputs.USED }}
with:
script: |
const review = process.env.PRIMARY_REVIEW || process.env.FALLBACK_REVIEW;
const label = process.env.PRIMARY_USED || process.env.FALLBACK_USED;
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## 🤖 AI Code Review — ${label}\n\n${review}`
});
- name: Print review (manual run)
if: github.event_name == 'workflow_dispatch'
env:
PRIMARY_REVIEW: ${{ steps.primary.outputs.REVIEW }}
PRIMARY_USED: ${{ steps.primary.outputs.USED }}
FALLBACK_REVIEW: ${{ steps.fallback.outputs.REVIEW }}
FALLBACK_USED: ${{ steps.fallback.outputs.USED }}
run: |
LABEL="${PRIMARY_USED:-$FALLBACK_USED}"
REVIEW="${PRIMARY_REVIEW:-$FALLBACK_REVIEW}"
echo "=== AI Review — $LABEL ==="
printf '%s\n' "$REVIEW"