Skip to content
Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Latest commit

 

History

22 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AI Commit Review

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.

Quickstart

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.

Set GitHub secrets

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 Code — CLAUDE_CODE_OAUTH_TOKEN

claude setup-token

Copy the printed token, then:

gh secret set CLAUDE_CODE_OAUTH_TOKEN

gh reads the token from stdin. Anthropic documents this in Generate a long-lived token.

Codex — CODEX_AUTH_JSON

Sign in locally with codex login. The file is ~/.codex/auth.json.

pbcopy < ~/.codex/auth.json
gh secret set CODEX_AUTH_JSON < ~/.codex/auth.json

Alternatively create a CODEX_ACCESS_TOKEN in ChatGPT admin settings. OpenAI documents both in Codex access tokens and Maintain Codex account auth in CI/CD.

Grok — GROK_AUTH_JSON

Sign in locally with grok login. The file is ~/.grok/auth.json.

pbcopy < ~/.grok/auth.json
gh secret set GROK_AUTH_JSON < ~/.grok/auth.json

Grok 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.

Agy — AGY_AUTH_JSON

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-token

On 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_JSON

Agy 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.

CLI auth modes

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 -p through Claude Code.
  • OpenAI CLI mode runs codex exec through Codex. The provider is still named openai in reports so existing digesting and agreement logic keeps working.
  • Grok CLI mode runs the official grok CLI in headless mode with structured output and read-only repository tools.
  • Agy CLI mode runs Google's agy CLI (Antigravity, successor to Gemini CLI) in headless print mode with structured output. Findings are still attributed to gemini in reports.

The default auth mode for Claude, OpenAI, Grok, and Agy is auto:

  • Claude uses CLI mode when claude-code-oauth-token is set; otherwise it uses anthropic-api-key.
  • OpenAI uses CLI mode when codex-access-token or codex-auth-json is set; otherwise it uses openai-api-key.
  • Grok uses CLI mode when grok-auth-json is set.
  • Agy uses CLI mode when agy-auth-json is set.
  • Set claude-auth or openai-auth to api or cli to force a mode. Set grok-auth: cli to use credentials already present in grok-home or the runner's default ~/.grok directory. Set agy-auth: cli to use credentials already present in agy-home or 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.

Example: force CLI modes

- 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 }}

Inputs

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.

Outputs

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.

Example: project-tuned

- 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-patterns and skip-author-patterns are bash glob patterns. Avoid [...] — bash treats it as a character class, not a literal substring. Write *skip-review*, not *[skip-review]*.

How it works

  1. Skip check — matches the commit subject and author against your skip patterns.
  2. Diff filtergit diff sha~1 sha with your exclude-paths applied. Skips if larger than max-diff-lines.
  3. 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.
  4. 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.

Permissions

The workflow needs:

permissions:
  contents: write       # for the fix PR branch
  issues: write         # for finding issues
  pull-requests: write  # for the fix PR

Notes

  • 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-failures and logged as warnings when at least one other provider succeeds. The action fails only when no selected provider produces a valid review.

License

MIT

About

GitHub Action that reviews a single commit with Claude, GPT, and Gemini in parallel

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages