DEV Community

dubleCC
dubleCC

Posted on • Originally published at heycc.cn

Using AI in GitHub Actions CI in 2026: The Claude Code Playbook

Originally published at heycc.cn. This is a mirrored copy — the canonical version is kept up to date at the source.

Using AI in GitHub Actions CI in 2026: The Claude Code Playbook

Putting an AI agent inside your CI pipeline is no longer a novelty experiment. In 2026 it is a concrete pattern: a GitHub Actions job that reads a pull request, reviews the diff, flags security issues, and sometimes pushes a fix commit — all before a human opens the PR. The catch is that the official Claude Code GitHub Action reached its v1 GA release, and that release renamed enough of the input surface that most blog snippets you will find still use the dead @beta form. What follows is the current configuration, the breaking-change mapping, and the security posture that keeps an AI agent with repository write access from becoming a liability — all checked against Anthropic's and GitHub's official docs (dates and links at the end).

What "AI in CI" actually means in 2026

There are three distinct jobs people lump together. Keeping them separate is the first decision you make.

Pattern Trigger What it does Write access needed
Interactive agent @claude mention in an issue/PR comment Implements a feature, fixes a bug, answers a question, opens a PR Yes (contents, PRs, issues)
Automated review pull_request opened/synchronize Reviews the diff and posts inline comments Only pull-requests: write
Security review pull_request AI-driven vulnerability scan of the diff contents: read + pull-requests: write

The interactive agent is the most powerful and the most dangerous: it can modify files and push branches. The two review patterns are read-mostly and are where most teams should start. You can run all three in the same repository as separate workflow files.

The v1 action: minimal working workflow

The action is anthropics/claude-code-action@v1. The smallest workflow that does something useful responds to @claude mentions in comments:

name: Claude Code
on:
  issue_comment:
    types: [created]
  pull_request_review_comment:
    types: [created]
jobs:
  claude:
    runs-on: ubuntu-latest
    steps:
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          # Responds to @claude mentions in comments
Enter fullscreen mode Exit fullscreen mode

That is the entire file. v1 auto-detects its mode: if you supply a prompt, it runs immediately in automation mode; if you omit it on a comment event, it waits for the trigger_phrase (default @claude) and runs interactively. There is no longer a mode: input.

The v1 input surface is deliberately small:

  • prompt — plain-text instructions or a skill invocation like /code-review:code-review.
  • claude_args — a passthrough for any Claude Code CLI flag (--max-turns, --model, --append-system-prompt, --allowedTools, --mcp-config).
  • anthropic_api_key — required for the direct Claude API path.
  • github_token, trigger_phrase, use_bedrock, use_vertex, plugin_marketplaces, plugins.

If you are upgrading from @beta

The GA release introduced breaking changes. If you copied a workflow before 2025, you must edit it. The mapping:

Old beta input v1 replacement
@beta @v1
mode: "tag" / "agent" removed — auto-detected
direct_prompt prompt
custom_instructions claude_args: --append-system-prompt
max_turns claude_args: --max-turns
model claude_args: --model
allowed_tools claude_args: --allowedTools

So a beta block with max_turns: "10" and model: "claude-sonnet-5" becomes:

- uses: anthropics/claude-code-action@v1
  with:
    prompt: "Review this PR for security issues"
    anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
    claude_args: |
      --append-system-prompt "Follow our coding standards"
      --max-turns 10
      --model claude-sonnet-5
Enter fullscreen mode Exit fullscreen mode

Pattern 2: automated review on every PR

To review every pull request without a human typing @claude, trigger on pull_request and pass a skill in prompt. The official example installs the code-review plugin from a marketplace and runs its namespaced skill:

name: Code Review
on:
  pull_request:
    types: [opened, synchronize]
jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          plugin_marketplaces: "https://github.com/anthropics/claude-code.git"
          plugins: "code-review@claude-code-plugins"
          prompt: "/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}"
Enter fullscreen mode Exit fullscreen mode

For a repo-local skill in .claude/skills/, run actions/checkout first, then pass /skill-name in prompt. If you have already adopted Claude Code locally — see our Claude Code best practices guide — the same skills and CLAUDE.md conventions carry straight into CI.

Pattern 3: AI security review

anthropics/claude-code-security-review is a dedicated action that scans the diff for vulnerabilities and posts findings as PR comments. The documented configuration:

name: Security Review
on: [pull_request]
permissions:
  contents: read
  pull-requests: write
jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha || github.sha }}
          fetch-depth: 2
      - uses: anthropics/claude-code-security-review@main
        with:
          claude-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
          comment-pr: true
Enter fullscreen mode Exit fullscreen mode

It exposes a findings-count output and writes a results JSON file, so you can fail the job on any finding, or read the JSON and gate only on certain severities. Note that the documented example pins to @main — for production you should pin to a commit SHA instead (covered below).

Gating merges on AI findings

A common mistake is assuming the AI check blocks the merge. Anthropic's managed Code Review (research preview for Team/Enterprise) deliberately does not: its check run, named "Claude Code Review," always completes with a neutral conclusion so it never blocks a merge on its own. Findings are posted as inline comments with severity markers — Important, Nit, Pre-existing.

If you want a hard gate, you read the machine-readable severity counts out of the check-run output in your own CI step and decide. A non-zero normal (Important) count means a real bug worth fixing before merge:

      - name: Gate on Important findings
        run: |
          CHECK_RUN_ID=$(gh api \
            repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha }}/check-runs \
            --jq '.check_runs[] | select(.name=="Claude Code Review") | .id')
          SEVERITY=$(gh api repos/${{ github.repository }}/check-runs/$CHECK_RUN_ID \
            --jq '.output.text | split("bughunter-severity: ")[1] | split(" -->")[0] | fromjson')
          IMPORTANT=$(echo "$SEVERITY" | jq '.normal')
          [ "$IMPORTANT" -eq 0 ] || exit 1
Enter fullscreen mode Exit fullscreen mode

You also steer the reviewer with two repo files. CLAUDE.md supplies project context, and newly introduced violations of it are flagged as nits. REVIEW.md is review-only and injected as highest-priority instructions — use it to redefine severity, cap nit volume, set skip rules, and add repo-specific checks like "new API routes must have an integration test." That file is the single highest-leverage thing you can add to tune an AI reviewer to your codebase.

Authentication without a static key

The old way: store ANTHROPIC_API_KEY as a repository secret and reference ${{ secrets.ANTHROPIC_API_KEY }}. It works and is fine for getting started. Never hardcode it in the workflow file.

The 2026 way: Workload Identity Federation (WIF), GA on the Claude Platform since June 17, 2026. The action exchanges the workflow's GitHub Actions OIDC token for a short-lived Anthropic access token, so there is no ANTHROPIC_API_KEY secret to create, store, or rotate. Bedrock and Vertex workflows use the same OIDC pattern against your cloud — no static cloud keys. It is not just a permission line, though — you register a federation issuer, service account, and federation rule once in the Claude Console (Settings → Workload identity), then point the action at them:

permissions:
  contents: write
  pull-requests: write
  id-token: write   # required to fetch the GitHub OIDC token
jobs:
  claude:
    steps:
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_federation_rule_id: fdrl_xxxxxxxxxxxx
          anthropic_organization_id: 00000000-0000-0000-0000-000000000000
          anthropic_service_account_id: svac_xxxxxxxxxxxx
          # anthropic_workspace_id is optional when the rule targets one workspace
Enter fullscreen mode Exit fullscreen mode

Do not also set anthropic_api_key or claude_code_oauth_token — a static credential takes precedence and the federation inputs are ignored.

Model IDs differ by backend. Direct API's current flagship is claude-sonnet-5. Bedrock prepends a region for cross-region inference (us.anthropic.claude-sonnet-4-6 in Anthropic's own Bedrock example, still on the Sonnet 4.6 generation there). Vertex uses the dated form (claude-sonnet-4-5@20250929). Pass whichever via claude_args: --model ....

The security checklist that actually matters

An AI agent in CI inherits whatever permissions the workflow has. Lock them down with GitHub's secure-use guidance.

Control Why it matters How
Least-privilege GITHUB_TOKEN Default-deny blast radius Set default token to read-only for contents, then grant the minimum via the permissions: key; anything you omit is set to none
SHA-pin every action Immutability against a backdoored tag Replace @v1/@main with a full-length commit SHA from the action's own repo (not a fork) — a SHA-1 collision would be required to forge it
Never check out untrusted code on pull_request_target That trigger runs privileged, with write access and secrets Use pull_request for fork PRs; if you must use pull_request_target, do not check out the PR's head
Org-level SHA-pinning policy Stops drift back to mutable tags Since Aug 2025, admins can enforce SHA pinning via the allowed-actions policy; unpinned workflows fail

The SHA-pinning point is worth internalizing. Pinning to a full-length commit SHA is currently the only way to use an action as a truly immutable release. @v1 is a moving tag; if the action's repo is compromised, @v1 can be repointed at malicious code, and your AI agent runs it with whatever permissions you granted. The Anthropic actions are reputable, but the principle is the same as any third-party dependency.

The pull_request_target row deserves the concrete version, because the mistake is easy to make by accident. Per GitHub's own security docs, a pull_request_target workflow "runs with elevated trust: the job receives the base repository's GITHUB_TOKEN and access to repository and organization secrets" — unlike plain pull_request, where GitHub "restricts these events to a read-only GITHUB_TOKEN, withholds access to other secrets." Cache access does not follow the same elevated pattern: the same docs note that pull_request_target workflows "have read-only access to the cache in the default branch's scope" and "can restore existing cache entries but cannot create or overwrite them." The exploit path, per GitHub Security Lab's writeup: if that privileged workflow then checks out the PR's head and runs anything from it — a build script, a test command, even a dependency install that executes a Makefile — an attacker who opened the PR from a fork controls what runs, with your repo's secrets and write token available to it. This is exactly the shape an AI-review workflow can fall into if it's adapted from a pull_request example without checking the trigger: the fix, per the same docs, is to use pull_request unless you specifically need the elevated token, and if you do need pull_request_target, treat the checked-out PR content as data to inspect, never as code to execute.

We tested the "omit it, get nothing" claim ourselves

The permissions: table above rests on one specific claim: a scope you don't list is set to none, not to whatever the repo's default happens to be. Rather than take that on faith from the docs, we built a two-workflow test and ran it on a real PR (github-actions-token-permissions-demo, public, reproducible by opening a PR against it yourself):

  • no-perms-test.yml sets permissions: {} and tries to post a PR comment with ${{ github.token }}.
  • write-perms-test.yml sets permissions: pull-requests: write and attempts the identical comment.

Both fired on the same pull request, seconds apart. The zero-permissions run failed with the token rejected before it ever reached the write itself:

GraphQL: Resource not accessible by integration (addComment)
##[error]Process completed with exit code 1.
Enter fullscreen mode Exit fullscreen mode

(run log)

The explicit-write run succeeded, and the resulting PR comment is still visible on PR #1 (run log):

posted by write-perms-test (expected to succeed) — GITHUB_TOKEN with explicit pull-requests:write worked.

Screenshot of the real GitHub PR: the no-perms-test check failing with a red X and write-perms-test passing with a green check, and the actual github-actions bot comment below them confirming the explicit-write token succeeded

Same repo, same event, same commit — the only variable was the permissions: block. That's the least-privilege default in practice, not just in the docs.

When to call the API directly instead

Sometimes you do not want the full agent — you want a single, deterministic LLM call inside a script step (classify a PR, draft release notes). Call the Messages API directly:

curl https://api.anthropic.com/v1/messages \
  -H "content-type: application/json" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -d '{"model":"claude-sonnet-5","max_tokens":1024,"messages":[{"role":"user","content":"Summarize this diff"}]}'
Enter fullscreen mode Exit fullscreen mode

The body requires model, max_tokens, and messages. max_tokens can be set to 0 solely to pre-warm the prompt cache without generating a response; otherwise it is the generation cap. Use the action when you want tool use, multi-turn iteration, and repo awareness; use the raw API when you want one bounded call with predictable cost.

A rollout order that fails safe

  • Start read-only. Add the security-review and PR-review patterns first. They cannot break your repo.
  • Add the interactive agent only once permissions are scoped and you trust the REVIEW.md / CLAUDE.md guardrails.
  • Gate explicitly. The AI check is neutral by design; if you want merges blocked, write the gh api --jq step yourself.
  • Pin SHAs before you scale across repos, ideally enforced by org policy.
  • Prefer OIDC/WIF over a long-lived API key once you are past the prototype.

Diagram of AI running inside a GitHub Actions CI pipeline: triggers feeding an ubuntu-latest runner that checks out code, authenticates via OIDC, runs the Claude Code Action and security review, then produces inline comments, a severity gate, and auto-fix commits, with a guardrails footer

The pattern that wins in 2026 is not "let the AI merge code." It is a tightly scoped agent that reviews, flags, and proposes — running under least privilege, pinned to a SHA, authenticated without a stored key, and gated by a check you control. Reviewing the AI's suggestions before merging is still in the official best-practices list for a reason.

What backs each claim

Read on 2026-06-28 against the documentation below, and re-verified on 2026-07-16 against the live docs and the claude-code-action repo's action.yml/docs/setup.md. The anthropics/claude-code-action@v1 inputs (prompt, claude_args, anthropic_api_key, github_token, trigger_phrase, use_bedrock, use_vertex) and the beta-to-v1 mapping come from the Claude Code GitHub Actions docs; the neutral check-run conclusion, severity-parsing jq filter, and REVIEW.md behavior from the Code Review docs; the security-review inputs from the claude-code-security-review repo; the WIF/federation inputs (anthropic_federation_rule_id, anthropic_organization_id, anthropic_service_account_id) from the action's own docs/setup.md and the Claude Platform WIF-for-GitHub-Actions guide; and the SHA-pinning, least-privilege GITHUB_TOKEN, and pull_request_target guidance from GitHub's secure-use reference and the August 2025 policy changelog. The 2026-07-16 pass corrected a pull_request_target/cache-access quote that had drifted from GitHub's current wording, refreshed the direct-API model ID to the current flagship (claude-sonnet-5; claude-sonnet-4-6 is now a legacy model per the models overview), replaced a guessed severity-gating gh api snippet with the documented one, corrected the max_tokens minimum (0 is valid, for cache pre-warming), and added the WIF setup steps that the auth section had glossed over. The Bedrock/Vertex model IDs (us.anthropic.claude-sonnet-4-6, claude-sonnet-4-5@20250929) still match Anthropic's own current GitHub Actions examples for those backends even though direct API has moved on — worth confirming against the model docs for your account before copying. Token rates are not quoted; only the Messages API request shape is. The "omit it, get nothing" permissions: behavior is not just cited from GitHub's docs — it's independently verified on 2026-07-08 in a real two-workflow test (see above), with both Actions run logs left public rather than pasted as unlinked text.

Sources

Top comments (0)