Set up the Claude Code GitHub Action properly
The Claude Code GitHub Action, anthropics/claude-code-action@v1, runs Claude Code inside an Actions job: with no prompt input it waits for an @claude mention, and with one it runs on whatever event starts the workflow. Set it up with id-token: write, the narrowest job permissions for the mode, and the Claude GitHub App rather than GITHUB_TOKEN, or CI will not run on the commits it pushes.
The minimal @claude workflow is three steps and works on the first try, which is why most repositories stop there. The parts that bite later are the ones the minimal version leaves implicit: which identity the commits are pushed as, what the job token can do, and who besides your team can start a run that holds your API key.
The opposite direction, Claude driving your CI from a terminal, is running CI from Claude Code, which also shows the minimal workflow. What follows is the full setup, with every input and error string read from the action at v1.0.273 and from Anthropic's documentation on 2026-11-12.

Two modes, chosen by one input
The mode input is gone; the action detects the mode itself. Without prompt it is interactive: it looks for the trigger phrase, @claude by default, in an issue or pull request comment, a pull request review, or the body or title of a newly opened issue, then replies in a comment it keeps updating. A label_trigger (default claude) and an assignee_trigger start the same flow from a label or an assignment.
With prompt set it is automation: Claude runs on any event, including schedule, and by default its results go to the workflow run log rather than a comment. A plain-text prompt starts with no shell and no GitHub API access; you grant the tools it needs with --allowedTools in claude_args. The if: on the job is not decoration: it keeps a runner from starting on every comment, and the action checks the phrase again itself.
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
jobs:
claude:
if: contains(github.event.comment.body, '@claude')
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@8a29bc6f0ae11a363721054b6739feba6adabe57 # v1.0.273
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
additional_permissions: |
actions: read
claude_args: --max-turns 20The inputs that matter
action.yml at v1.0.273 declares 40 inputs. These are the ones that change what the run can do or who can start it.
| Input | Default | What it controls |
|---|---|---|
prompt | empty | Set: automation mode. Empty: wait for the trigger phrase |
claude_args | empty | CLI flags: --model, --max-turns, --allowedTools, --permission-mode |
anthropic_api_key / claude_code_oauth_token | none | Claude API key, or a subscription token from claude setup-token |
anthropic_federation_rule_id + anthropic_organization_id | none | Workload identity federation: no stored Anthropic key |
use_bedrock / use_vertex / use_foundry | false | Route inference through a cloud provider, authenticated by OIDC |
github_token | none | Omitted: the action authenticates as the Claude GitHub App |
allowed_bots | empty (no bots) | Bot actors allowed to trigger it; * allows all |
allowed_non_write_users | empty | Users without write access; only works with github_token |
restrict_untrusted_prs | true | Denies package managers, test runners and shells on pull requests from authors without write access |
track_progress | false | Tracking comment on pull_request and issues events in automation mode |
Authentication is two separate questions
The first is how Claude reaches a model: an Anthropic API key, a Claude subscription OAuth token, workload identity federation against a Claude Console service account, or Amazon Bedrock, Google Cloud's Agent Platform (use_vertex) or Microsoft Foundry, each through OIDC. The second is how the action reaches GitHub: by default it requests an OIDC token, exchanges it with Anthropic for a Claude GitHub App installation token, and pushes and comments as claude[bot].
Both answers can need id-token: write, which is why the permission sits in every example. Leave it out on the default path and the run stops with "Could not fetch an OIDC token. Did you remember to add id-token: write to your workflow permissions?" A second message confuses people on the first pull request: when the workflow file itself is new or changed, the exchange refuses and the action logs that this is expected and that the workflow will begin working once you merge your PR.
With a cloud provider, Anthropic's documentation has you choose the GitHub identity yourself: the official app, a custom app you register with only Contents, Issues and Pull requests, or GITHUB_TOKEN. The official app is installed with its full permission set, including Actions and Workflows write, because GitHub does not let you accept a subset; a custom app is the way to hold the action to three permissions.
The identity also decides whether CI sees Claude's work. Branches pushed with GITHUB_TOKEN start no push run, so keep the App or a custom app token for any workflow that commits; running CI from Claude Code has the rule and the fix, and required checks for AI pull requests has what it takes for the resulting check to gate a merge.
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- id: app-token
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
with:
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6.3.0
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: us-west-2
- uses: anthropics/claude-code-action@8a29bc6f0ae11a363721054b6739feba6adabe57 # v1.0.273
with:
github_token: ${{ steps.app-token.outputs.token }}
use_bedrock: "true"
claude_args: '--model us.anthropic.claude-sonnet-4-6'The minimum permissions for each job
Job permissions govern the job's own GITHUB_TOKEN, not the app token the action exchanges for. That is why Anthropic's code-review example posts inline comments from a job that holds only read scopes plus id-token: write: the comments are written with the Claude App's token. Grant write scopes only where Claude must commit, and only on the job that runs it.
on:
pull_request:
types: [opened, synchronize, ready_for_review, reopened]
jobs:
review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@8a29bc6f0ae11a363721054b6739feba6adabe57 # v1.0.273
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
plugin_marketplaces: "https://github.com/anthropics/claude-plugins-official.git"
plugins: "code-review@claude-plugins-official"
prompt: "/code-review:code-review --comment ${{ github.repository }}/pull/${{ github.event.pull_request.number }}"
claude_args: '--allowedTools "mcp__github_inline_comment__create_inline_comment"'| Workflow | Job permissions | Why |
|---|---|---|
@claude that may edit code | contents: write, pull-requests: write, issues: write, id-token: write | Commits, comments, App token exchange |
| Add CI access to the above | actions: read plus additional_permissions | Reads run status and job logs |
| Review on every pull request | contents: read, pull-requests: read, issues: read, id-token: write | Writes go through the App token |
| Scheduled report to the run log | contents: read, issues: read, id-token: write | No writes at all |
Restricting who can start it
Before Claude starts, the action runs two checks on the actor. On issue and pull request events the user must have write access; a commenter without it gets Actor does not have write permissions to the repository. On every event, including schedule, a bot actor is refused with a message that starts Workflow initiated by non-human actor: and ends Add bot to allowed_bots list or use '*' to allow all bots. unless you list it.
Both escape hatches carry warnings in the action's own security document. Bots in allowed_bots are not checked for repository permissions at all, so on a public repository '*' lets any GitHub App that can comment drive Claude with a prompt it controls. allowed_non_write_users bypasses the write check, works only with github_token, and should only ever run with workflow permissions as small as issue labeling.
On pull requests whose author lacks write access, restrict_untrusted_prs (on by default) denies the commands that run code from the checkout, even if claude_args allows them; Claude can still read, edit and push. On public repositories, include_comments_by_actor narrows whose comments reach the prompt at all. What an outsider's text can do once it does reach Claude, and the two-job split that contains it, is on prompt injection in CI agents.
What stands behind this page
Source code and Anthropic's documentation, not a recorded run. Nothing in content/repro-evidence.mjs or content/heal-evidence.mjs covers this slug, so there is no captured log here and no repair claim. Every input, default and error message above was read from anthropics/claude-code-action at v1.0.273 (action.yml, docs/ and src/) and from code.claude.com on 2026-11-12; running the workflows would need an Anthropic key and an installed app, and the log they produce would show one account rather than the action's rules.
Key takeaways
- No
promptmeans interactive (@claude); apromptmeans automation on any event, with results in the run log by default. id-token: writeis needed for the default App authentication, for federation and for every cloud provider.- Job permissions limit
GITHUB_TOKENonly; the App token does the writing, so review jobs can stay read-only. - Pass
github_token: ${{ secrets.GITHUB_TOKEN }}and pushes start no CI; use the Claude App or a custom app token.
Frequently asked questions
Why is Claude not responding to @claude comments?
@claude as a whole word (not @claude-bot or /claude), and the commenter has write access. A comment from an account without write access fails the action's permission check before Claude starts.Can I use my Claude Max subscription with the Claude Code GitHub Action?
claude setup-token locally, store the result as the CLAUDE_CODE_OAUTH_TOKEN secret, and pass it to the claude_code_oauth_token input instead of anthropic_api_key; Anthropic lists Pro, Max, Ultra, Team and Enterprise plans as eligible, and runs then use the subscription rather than API billing. For a secret shared across repositories, use an API key, because the token is tied to the subscription of whoever generated it.Why am I getting OIDC authentication errors from claude-code-action?
id-token: write. When it is missing the action prints "Could not fetch an OIDC token. Did you remember to add id-token: write to your workflow permissions?" Bedrock, Agent Platform, Foundry and workload identity federation need the same permission.Can I run the Claude Code GitHub Action with Bedrock or Vertex AI?
use_bedrock: "true" or use_vertex: "true" (Foundry is use_foundry), authenticate to the cloud first with its own OIDC action, grant id-token: write, and pass the provider's model ID through claude_args. Anthropic's examples pair this with a custom GitHub App token passed as github_token.