GitHub Action that reviews a single commit with Claude, GPT, Agy (Gemini), and Grok, deduplicates findings, files them as a GitHub Issue, and optionally opens a draft PR with high-confidence fixes that multiple models agree on.
The action reviews one commit per invocation. Your workflow enumerates the SHAs and fans them out in a matrix so one failing review does not cancel the rest.
Save this as .github/workflows/ai-commit-review.yml. It is the full workflow, not a fragment: an enumerate job lists every commit in the push, then a review job runs this action once per SHA.
name: AI Commit Review
on:
push:
branches: [main]
permissions:
contents: write
issues: write
pull-requests: write
jobs:
# One job lists the SHAs. The action itself never walks history.
enumerate:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.commits.outputs.matrix }}
count: ${{ steps.commits.outputs.count }}
steps:
- uses: actions/checkout@v6
with: { fetch-depth: 0 }
- id: commits
env:
BEFORE: ${{ github.event.before }}
AFTER: ${{ github.event.after }}
run: |
if [[ "$BEFORE" == "0000000000000000000000000000000000000000" ]]; then
SHAS=$(git log --format='%H' -1 "$AFTER")
else
SHAS=$(git log --format='%H' "${BEFORE}..${AFTER}")
fi
MATRIX=$(echo "$SHAS" | jq -R -s -c 'split("\n") | map(select(. != "")) | map({sha: .})')
echo "count=$(echo "$MATRIX" | jq 'length')" >> "$GITHUB_OUTPUT"
echo "matrix=${MATRIX}" >> "$GITHUB_OUTPUT"
# One matrix cell per commit. fail-fast: false keeps later SHAs running
# if an earlier review fails. That is what "failures isolate per-commit" means.
review:
needs: enumerate
if: needs.enumerate.outputs.count != '0'
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 5
matrix:
commit: ${{ fromJson(needs.enumerate.outputs.matrix) }}
steps:
- uses: actions/checkout@v6
with: { fetch-depth: 0 }
- uses: leek/ai-commit-review@v1
with:
commit-sha: ${{ matrix.commit.sha }}
claude-code-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
codex-auth-json: ${{ secrets.CODEX_AUTH_JSON }}
agy-auth-json: ${{ secrets.AGY_AUTH_JSON }}
grok-auth-json: ${{ secrets.GROK_AUTH_JSON }}A push of five commits becomes five independent review jobs. If commit 3's provider call dies, commits 1, 2, 4, and 5 still finish. A single-job loop over SHAs would stop at the first failure.
Any provider whose credentials are empty is skipped. Run with any combination of the four.
Model inputs default to empty so Claude Code, Codex, Agy, and Grok pick the model for the authenticated account. Set claude-model, openai-model, gemini-model, or grok-model only when you want to pin one. API mode still falls back to claude-opus-5 and gpt-5.6 if those inputs are empty.
Create repository secrets under Settings → Secrets and variables → Actions → New repository secret, or pipe them with gh. Use these names with the workflow above.
Do this only for private repositories and trusted runners. Do not put CLI account credentials in public repos or fork-triggered workflows.
claude setup-tokenCopy the printed token, then:
gh secret set CLAUDE_CODE_OAUTH_TOKENgh reads the token from stdin. Anthropic documents this in Generate a long-lived token.
Sign in locally with codex login. The file is ~/.codex/auth.json.
pbcopy < ~/.codex/auth.json
gh secret set CODEX_AUTH_JSON < ~/.codex/auth.jsonAlternatively create a CODEX_ACCESS_TOKEN in ChatGPT admin settings. OpenAI documents both in Codex access tokens and Maintain Codex account auth in CI/CD.
Sign in locally with grok login. The file is ~/.grok/auth.json.
pbcopy < ~/.grok/auth.json
gh secret set GROK_AUTH_JSON < ~/.grok/auth.jsonGrok rotates the refresh token on local use. Re-copy ~/.grok/auth.json into the secret after you next run grok locally, or CI will get invalid_grant / "Not signed in". See the Grok CLI authentication guide.
Sign in locally with agy.
On Linux the file is ~/.gemini/jetski-standalone-oauth-token:
pbcopy < ~/.gemini/jetski-standalone-oauth-token
gh secret set AGY_AUTH_JSON < ~/.gemini/jetski-standalone-oauth-tokenOn macOS Agy stores that JSON in Keychain (service=gemini, account=antigravity). Copy it with:
python3 -c '
import base64, subprocess, sys
raw = subprocess.check_output(
["security", "find-generic-password", "-s", "gemini", "-a", "antigravity", "-w"],
text=True,
).strip()
prefix = "go-keyring-base64:"
sys.stdout.buffer.write(
base64.b64decode(raw[len(prefix):]) if raw.startswith(prefix) else raw.encode()
)
' | tee >(pbcopy) | gh secret set AGY_AUTH_JSONAgy access tokens last about an hour. Run agy locally so it refreshes Keychain, then replace the secret when CI starts getting auth errors. See Installation & auth.
Claude and OpenAI can run through their local coding CLIs instead of direct API calls. Grok and Agy run through their CLIs only:
- Claude CLI mode runs
claude -pthrough Claude Code. - OpenAI CLI mode runs
codex execthrough Codex. The provider is still namedopenaiin reports so existing digesting and agreement logic keeps working. - Grok CLI mode runs the official
grokCLI in headless mode with structured output and read-only repository tools. - Agy CLI mode runs Google's
agyCLI (Antigravity, successor to Gemini CLI) in headless print mode with structured output. Findings are still attributed togeminiin reports.
The default auth mode for Claude, OpenAI, Grok, and Agy is auto:
- Claude uses CLI mode when
claude-code-oauth-tokenis set; otherwise it usesanthropic-api-key. - OpenAI uses CLI mode when
codex-access-tokenorcodex-auth-jsonis set; otherwise it usesopenai-api-key. - Grok uses CLI mode when
grok-auth-jsonis set. - Agy uses CLI mode when
agy-auth-jsonis set. - Set
claude-authoropenai-authtoapiorclito force a mode. Setgrok-auth: clito use credentials already present ingrok-homeor the runner's default~/.grokdirectory. Setagy-auth: clito use credentials already present inagy-homeor the runner's default~/.gemini/jetski-standalone-oauth-token.
The action installs missing claude, codex, grok, and agy commands when their CLI mode is selected. Set install-cli-tools: false if your runner already has them.
CLI modes run from the caller's checked-out repository and receive the commit-sha explicitly. Use actions/checkout with fetch-depth: 0 so the CLIs can inspect the commit and surrounding repository context. Unlike API mode, CLI mode does not embed the filtered diff in the prompt; Claude, Codex, Grok, and Agy inspect the commit from the checkout themselves. Claude, Grok, and Agy are limited to read/search/git shell tools. Codex defaults to codex-sandbox: danger-full-access because its Linux read-only sandbox depends on user namespaces that may be unavailable on GitHub-hosted runners. Grok uses --sandbox read-only when Linux user namespaces work. GitHub-hosted Ubuntu often blocks bwrap uid maps, in which case the action falls back to --sandbox off. Agy CLI mode seeds both the Keychain {token, auth_method} wrapper and the inner oauth2.Token under ~/.gemini/, then runs with a fake SSH session and no D-Bus so print-mode reads those files instead of Secret Service. Grok CLI mode writes auth.json under an isolated GROK_HOME, duplicates the session under the legacy https://accounts.x.ai/sign-in key, and passes the credential as GROK_AUTH so headless grok does not depend on issuer::client_id scope lookup. Claude CLI mode writes projects["<checkout>"].hasTrustDialogAccepted: true to ~/.claude.json so permissions.allow rules in the repo's .claude/settings.json apply in headless -p (there is no interactive trust dialog on a runner). The action verifies afterward that the checkout has no tracked changes and no unexpected untracked files.
Codex CLI mode writes project_doc_fallback_filenames = ["CLAUDE.md"] to CODEX_HOME/config.toml, so repositories that use CLAUDE.md instead of AGENTS.md are still picked up by Codex's project-doc discovery.
- uses: leek/ai-commit-review@v1
with:
commit-sha: ${{ matrix.commit.sha }}
claude-auth: cli
openai-auth: cli
claude-code-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
codex-auth-json: ${{ secrets.CODEX_AUTH_JSON }}
grok-auth-json: ${{ secrets.GROK_AUTH_JSON }}
agy-auth-json: ${{ secrets.AGY_AUTH_JSON }}| Input | Default | Description |
|---|---|---|
commit-sha |
required | Commit SHA to review. Caller must actions/checkout with fetch-depth: 0. |
anthropic-api-key |
empty | Anthropic API key. Provider runs only when set. |
openai-api-key |
empty | OpenAI API key. |
claude-auth |
auto |
Claude provider auth mode: auto, api, or cli. |
openai-auth |
auto |
OpenAI provider auth mode: auto, api, or cli. |
grok-auth |
auto |
Grok provider auth mode: auto or cli. auto selects Grok when grok-auth-json is set. |
agy-auth |
auto |
Agy provider auth mode: auto or cli. auto selects Agy when agy-auth-json is set. |
claude-code-oauth-token |
empty | Claude Code OAuth token from claude setup-token. Enables Claude CLI mode in auto. |
codex-access-token |
empty | Codex access token passed as CODEX_ACCESS_TOKEN. Enables Codex CLI mode in auto. |
codex-auth-json |
empty | Contents of a Codex auth.json file for Codex CLI mode. Use only on trusted private runners. |
codex-home |
empty | Optional CODEX_HOME path for Codex CLI mode. Useful for self-hosted runners with persistent auth. |
grok-auth-json |
empty | Contents of a Grok auth.json file. Enables Grok CLI mode in auto. Use only on trusted private runners. |
grok-home |
empty | Optional GROK_HOME path for Grok CLI mode. Useful for self-hosted runners with persistent auth. |
agy-auth-json |
empty | Contents of Agy's jetski-standalone-oauth-token JSON. Enables Agy CLI mode in auto. Use only on trusted private runners. |
agy-home |
empty | Optional HOME path for Agy CLI mode. Agy reads ~/.gemini/jetski-standalone-oauth-token from this home. Useful for self-hosted runners with persistent auth. |
install-cli-tools |
true |
Install missing Claude Code, Codex, Grok, or Agy CLI tools when a CLI mode is selected. |
claude-cli-path |
claude |
Claude Code CLI command path used in Claude CLI mode. |
codex-cli-path |
codex |
Codex CLI command path used in OpenAI CLI mode. |
grok-cli-path |
grok |
Grok CLI command path used in Grok CLI mode. |
agy-cli-path |
agy |
Agy CLI command path used in Gemini CLI mode. |
codex-sandbox |
danger-full-access |
Codex sandbox mode used in OpenAI CLI mode. Use read-only only on runners where Codex's Linux sandbox can create user namespaces. |
claude-model |
empty | Optional Claude model id. Empty lets Claude Code pick in CLI mode. |
openai-model |
empty | Optional OpenAI/Codex model id. Empty lets Codex pick in CLI mode. |
gemini-model |
empty | Optional Agy model slug. Empty uses the Agy CLI default. |
grok-model |
empty | Optional Grok model id. Empty uses the Grok CLI default. |
claude-context-file |
empty | Project context file injected into the Claude prompt. |
openai-context-file |
empty | Project context file injected into the OpenAI prompt. |
gemini-context-file |
empty | Project context file injected into the Agy prompt. |
grok-context-file |
empty | Project context file injected into the Grok prompt. |
prompt-file |
empty | Path to a custom prompt template. Overrides the bundled generic prompt. |
exclude-paths |
empty | Newline-separated git pathspecs excluded from the diff. Use the :!path syntax. |
max-diff-lines |
5000 |
Skip review if filtered diff exceeds this many added/changed lines. |
skip-message-patterns |
Merge* |
Newline-separated bash globs matched against the commit subject. |
skip-author-patterns |
empty | Newline-separated bash globs matched against the commit author name. |
min-severity-for-issue |
warning |
One of critical, warning, info. |
min-models-for-fix-pr |
2 |
Number of providers that must agree on a high-confidence fix before a fix PR is opened. 0 disables. |
issue-label |
ai-review |
Label applied to created issues. |
issue-title-prefix |
[AI Review] |
Issue title prefix. |
fix-pr-title-prefix |
[AI Fix] Suggested fixes for |
Fix PR title prefix. |
fix-branch-prefix |
ai-fix/ |
Fix branch prefix. Short SHA is appended. |
base-branch |
main |
Base branch for fix PRs. |
github-token |
${{ github.token }} |
Token used to create issues, comments, branches, and PRs. |
node-version |
20 |
Node.js version. |
| Output | Description |
|---|---|
reviewed |
true if the commit was reviewed, false if skipped. |
skip-reason |
Reason the commit was skipped, if any. |
diff-line-count |
Added/changed line count of the filtered diff. |
critical-count |
Critical findings after dedup. |
warning-count |
Warning findings after dedup. |
info-count |
Info findings after dedup. |
issue-url |
URL of the created issue, if any. |
fix-pr-url |
URL of the created draft fix PR, if any. |
provider-failures |
Comma-separated provider names that failed to produce a valid review. |
provider-successes |
Comma-separated provider names that produced a valid review. |
provider-skips |
Comma-separated provider names skipped because credentials or supported auth modes were not provided. |
- uses: leek/ai-commit-review@v1
with:
commit-sha: ${{ matrix.commit.sha }}
claude-code-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
codex-auth-json: ${{ secrets.CODEX_AUTH_JSON }}
agy-auth-json: ${{ secrets.AGY_AUTH_JSON }}
grok-auth-json: ${{ secrets.GROK_AUTH_JSON }}
claude-context-file: CLAUDE.md
openai-context-file: AGENTS.md
gemini-context-file: GEMINI.md
grok-context-file: AGENTS.md
prompt-file: .github/ai-review-prompt.txt
exclude-paths: |
:!package-lock.json
:!yarn.lock
:!vendor/
:!node_modules/
:!tests/
skip-message-patterns: |
Merge*
build(deps)*
*skip-review*
*skip-ci*
*fix code style*
*Fix Code Style*
ai-review:*
skip-author-patterns: |
*dependabot*Pattern syntax:
skip-message-patternsandskip-author-patternsare bash glob patterns. Avoid[...]— bash treats it as a character class, not a literal substring. Write*skip-review*, not*[skip-review]*.
- Skip check — matches the commit subject and author against your skip patterns.
- Diff filter —
git diff sha~1 shawith yourexclude-pathsapplied. Skips if larger thanmax-diff-lines. - Provider fan-out — runs Claude, GPT/OpenAI, Agy, and Grok in sequence. Claude and OpenAI can use direct API calls or their CLI modes; Grok and Agy use their CLIs. Each provider receives the bundled (or custom) prompt and optional project context. API providers receive the diff, while CLI providers inspect the checked-out commit.
- Digest — merges findings, dedupes by file + line proximity + severity, builds a markdown report, files it as an issue. Optionally opens a draft fix PR for high-confidence findings that multiple models agree on.
The workflow needs:
permissions:
contents: write # for the fix PR branch
issues: write # for finding issues
pull-requests: write # for the fix PR- The action does not enumerate commits. Drive the matrix from your workflow so failures isolate per-commit. The Quickstart file is that workflow.
- Existing issues for the same short SHA are detected and creation is skipped.
- All API calls have two retries on 5xx responses.
- CLI modes normalize their output through the same findings parser and self-retraction filter as API modes.
- Selected providers that fail to produce valid review JSON are reported in
provider-failuresand logged as warnings when at least one other provider succeeds. The action fails only when no selected provider produces a valid review.
MIT