Claude Code GitHub Actions Permissions: Least-Privilege YAML
Claude Code GitHub Actions permissions come in two layers. The first is the Claude GitHub App, which needs Contents, Issues and Pull requests set to read and write. The second is the permissions: block in your workflow, which scopes the GITHUB_TOKEN and must include id-token: write for the action's default authentication. On top of those, claude_args: --allowedTools decides which tools Claude can actually run, and your Anthropic credential sits in a GitHub secret. Below you'll find the exact YAML for review, scheduled and code-writing jobs, plus a least-privilege table built from the official Claude Code GitHub Actions docs.
Which option is right for you?
Pick the job you actually want automated and start from the matching official example. Don't start from a broad template and trim it down.
| Your situation | What to pick first | Limitation / not for | First page to open |
|---|---|---|---|
| Beginner, one repo, already use Claude Code locally | Run /install-github-app (quick setup) | Installs the official app with its full permission set, including Actions, Workflows and Repository hooks write, and you can't accept a subset. Not for you if you want only the three scopes the action uses (build a custom app instead) | Claude Code GitHub Actions: Quick setup |
| You want automatic PR review and nothing else | Code-review skill workflow with read-only token scopes | Comments only and never pushes fixes. On public repos it doesn't run on fork PRs because secrets are withheld. Not for you if you want Claude to fix what it finds | Run a skill example |
Daily work: @claude fixes issues and pushes commits | Interactive mode workflow (examples/claude.yml) | Needs write scopes on the workflow token, only users with write access can trigger it, and it can't edit .github/workflows/. Not for repos where untrusted users comment | examples/claude.yml |
| Recurring report or triage on a cron | Automation mode with a prompt and named MCP tools | Runs only from the default branch. In public repos it is disabled after 60 days of inactivity. Output stays in the run log unless you grant a posting tool. Not for one-off requests | Run on a schedule |
| Tight budget, already pay for Pro/Max | CLAUDE_CODE_OAUTH_TOKEN from claude setup-token | Tied to the subscription of whoever generated it and subject to plan usage limits. Not for org-wide shared secrets | Authentication docs |
| Company with an AWS/GCP/Azure account and a no-static-keys rule | Bedrock, Vertex or Foundry via OIDC | Needs a cloud role plus a custom GitHub App. Not for quick experiments or anyone without cloud admin access | Cloud providers page |
What permissions does Claude Code GitHub Actions need?
It needs two things: the Claude GitHub App and a workflow permissions: block that includes id-token: write. The app needs Contents, Issues and Pull requests set to read and write. The app and the workflow token are two separate systems, and most permission confusion comes from mixing them up.
The first layer is the Claude GitHub App. The docs state the action "relies on three of the app's permissions": Contents, Issues and Pull requests, all read and write (Claude Code docs). When you install the app you actually grant a bigger set, because the app is shared with Code Review and web auto-fix. It includes Actions (read and write), Checks (read and write), Workflows (read and write), Repository hooks (read and write) and several read-only scopes. You can't trim that set: "GitHub doesn't let you accept a subset" (Claude Code docs). If that's a problem for you, the docs point to building a custom GitHub App with only Contents, Issues and Pull requests. That app works for the action only, though. Code Review and web auto-fix still need the official app.
The second layer is the workflow's GITHUB_TOKEN scopes. These come from the permissions: key at workflow or job level. GitHub's rule is strict: "If you specify the access for any of these permissions, all of those that are not specified are set to none" (GitHub workflow syntax). So once you write a block, list every scope you need.
| Layer | Where you set it | What it controls | Minimum for this action |
|---|---|---|---|
| Claude GitHub App | github.com/apps/claude install screen | What the app token can do in the repo | Contents, Issues, Pull requests: read and write |
Workflow permissions: | .github/workflows/*.yml | GITHUB_TOKEN scopes and the OIDC token | id-token: write plus the read scopes your job needs |
claude_args --allowedTools | with: input of the action step | Which tools Claude may call | Only the tools the task uses |
The id-token: write scope is easy to miss. The official example labels it "required for the Claude Code GitHub Action's default GitHub App authentication" (Claude Code docs). Without it you get OIDC errors, as the action FAQ explains.
Two limits apply whatever you grant. Per the FAQ, the app has no workflow write access through the action, so Claude won't edit .github/workflows/. It also won't rebase, merge or force-push, even if you allow Bash(git rebase:*), because that rule lives in its system prompt (action FAQ).
How do you set a least-privilege permissions block for Claude Code?
Start with read-only scopes plus id-token: write. Add contents: write, pull-requests: write or issues: write only for jobs that change code or post with the GITHUB_TOKEN. The Claude Code docs and the security doc give 4 example blocks, and they show how far apart the needs are.
| Job type | permissions: block (from official examples) | Why |
|---|---|---|
| PR review via code-review skill | contents: read, pull-requests: read, issues: read, id-token: write | The app token posts the inline comments, so the workflow token only reads |
| Scheduled report | contents: read, issues: read, id-token: write | Reads commits and issues through GitHub MCP tools; output goes to the run log |
@claude interactive (writes code) | contents: write, pull-requests: write, issues: write, id-token: write, actions: read | Pushes commits and updates comments; actions: read lets Claude see CI results |
| Issue labeling for non-write users | issues: write only, with github_token passed | The security doc's example of an "extremely limited" workflow |
Sources: Claude Code GitHub Actions docs for the first three rows and action security doc for the last.
Here's the interactive block as the docs publish it, set at job level:
jobs:
claude:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read
A few things trip people up:
- Job-level vs workflow-level. GitHub accepts
permissions"for an entire workflow or for individual jobs" (GitHub Docs). Put it on the Claude job so that other jobs in the same file don't inherit write access. - Missing
actions: read. If you want Claude to read CI failures, you need theactions: readscope and the action inputadditional_permissions: "actions: read", which is "for viewing workflow results" (usage.md). - Using
write-allfor convenience. That gives the token every scope. The docs' own advice is to "grant theGITHUB_TOKENthe least required access" (GitHub Docs).
If you also run Claude headless on your own machine, the same mindset applies to local settings. See our headless permissions template.
How do you use allowed_tools and claude_args in Claude Code GitHub Actions?
In v1 of Claude Code GitHub Actions, tool limits and every other CLI flag go through claude_args (usage.md). The old allowed_tools input is deprecated. Pass --allowedTools with the specific tools and Bash patterns the job needs.
The old input is marked "DEPRECATED: Use claude_args with --allowedTools instead" (usage.md). The claude_args input "accepts any Claude Code CLI argument" (Claude Code docs), including --max-turns, --model, --mcp-config, --allowedTools (alias --allowed-tools) and --debug.
Defaults matter here. "The Bash tool is disabled by default for security" (action FAQ). For a plain-text automation prompt, the docs say Claude "has no shell or GitHub API access until you grant the tools the prompt needs" (Claude Code docs). So you're starting from a closed position and opening what each job needs.
| Task | Example --allowedTools value | Notes |
|---|---|---|
| Post inline review comments | mcp__github_inline_comment__create_inline_comment | Required even if the skill already lists it; the MCP server only starts when this flag names it |
| Read commits and issues for a report | mcp__github__list_commits,mcp__github__list_issues | From the official daily-report example |
| Run tests and lint | Bash(npm test:),Bash(npm run lint:) | Example; narrower than the FAQ's Bash(npm:*) |
| Read an issue for triage | Bash(gh issue view:*) | The security doc's recommended scope for non-write users |
| Edit files | Edit,Read,Write | From the migration guide example in usage.md |
Here is a multi-line claude_args example. Replace <model-id> with a model ID your account can use. This article doesn't recommend a specific model ID.
claude_args: |
--model <model-id>
--max-turns 8
--allowedTools "Read,Edit,Bash(npm test:*),Bash(npm run lint:*)"
Common mistakes and fixes:
- **
Bash(npm:*)is wider than it looks.** It coversnpm installandnpm publishas well asnpm test. Name subcommands when you can. Our allowed-tools syntax guide covers how the pattern matching works. - Review posts nothing. Either the
--commentargument is missing from the review prompt, orclaude_argsdoesn't name the inline-comment MCP tool. Without--comment, "Claude posts nothing" and findings stay in the run log (Claude Code docs). - Still using
@betakeys.direct_prompt,mode,max_turnsandmodelare beta-era inputs. Move them topromptandclaude_args. Thecustom_instructionsinput becomes--append-system-prompt(upgrade section).
You can also pass tool rules as JSON through the settings input with a permissions.allow rule, if you'd rather keep them in a settings file (Claude Code docs).
How do you store the Anthropic API key safely as a GitHub secret?
Save the credential as a GitHub Actions secret and reference it only as ${{ secrets.NAME }}. Name it ANTHROPIC_API_KEY (or CLAUDE_CODE_OAUTH_TOKEN for a subscription token). The action's security doc puts it bluntly: "Never hardcode your Anthropic API key or OAuth token in workflow files!" (security.md).
These are the steps for a repository secret, using GitHub's UI labels (GitHub Docs):
- Open the repo and click Settings (or the More dropdown if Settings isn't shown).
- In the sidebar's Security section, click Secrets and variables, then Actions.
- On the Secrets tab, click New repository secret.
- Enter
ANTHROPIC_API_KEYas Name, paste the key from the Claude Console as Secret, and click Add secret. - In the workflow, add
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}underwith:.
For a subscription token, run claude setup-token locally, save the output as CLAUDE_CODE_OAUTH_TOKEN, and use the input claude_code_oauth_token instead. The docs list it for Pro, Max, Ultra, Team and Enterprise plans (Claude Code docs).
If you want a human gate on the riskier workflows, use an environment secret. Go to Settings → Environments, pick the environment, and under Environment secrets click Add secret (GitHub Docs). Then add environment: <name> to the job and set protection rules for that environment, described in GitHub's "Managing environments for deployment" page.
Pitfalls:
- Sharing an OAuth token across an org. The docs recommend a Console API key for shared secrets, since an OAuth token is tied to the subscription of whoever ran
claude setup-token. - Thinking deleted means revoked. Deleting the GitHub secret leaves the credential valid. To retire it, also delete the key in the Console (Claude Code docs).
- Leaking via logs.
show_full_outputis off by default, and the security doc warns that logs "are publicly visible in GitHub Actions for public repositories!" (security.md). Leave it off unless you're debugging a private repo.
How do you set up an unattended PR review workflow with Claude Code?
Run the official code-review plugin on a pull_request trigger with read-only scopes. Add id-token: write to the workflow scopes and a claude_args line that allows only the inline-comment tool. This is the official example, with two optional additions marked in comments:
name: Code Review
on:
pull_request:
types: [opened, synchronize, ready_for_review, reopened]
paths: # example addition: only review source changes
- "src/**"
jobs:
review:
runs-on: ubuntu-latest
timeout-minutes: 15 # example addition: cap runaway runs
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
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"'
The base file, including actions/checkout@v6, fetch-depth: 1 and claude-code-action@v1, comes from the Claude Code docs, "Run a skill".
Steps:
- Install the Claude GitHub App on the repo.
- Add the
ANTHROPIC_API_KEYsecret (previous section). - Save the file as
.github/workflows/claude-code-review.ymlon a branch and open a PR. That PR should trigger the first review. - Check the run under the Actions tab, then look for inline comments on the PR.
Some behavior is built in. Claude skips draft and closed PRs, PRs it judges trivial or automated, and PRs that already have a Claude comment. On public repos, the docs note GitHub withholds secrets from fork PRs, "so the review runs only on pull requests from branches in the same repository" (Claude Code docs).
To limit by branch, add branches: [main] under pull_request. That matches the PR's base branch. Put your review criteria in CLAUDE.md and keep the file short, because Claude reads it on every run. Our CLAUDE.md template for unattended agents has a structure you can copy.
If you'd rather not maintain a workflow at all, Anthropic's separate Code Review product runs reviews without a workflow file.
How do you run Claude Code on a schedule or on issues without babysitting?
Give Claude Code a prompt (automation mode) on schedule or issues triggers. Pair it with narrow scopes, --max-turns and timeout-minutes so each run is bounded. Use @claude mentions (interactive mode) only when a human starts each request.
| Interactive mode | Automation mode | |
|---|---|---|
| How it starts | No prompt input; waits for @claude | prompt input set; runs on the event |
| Where results go | Comment on the issue or PR | Workflow run log, unless the prompt and a tool let it post |
| Good for | Ad-hoc fixes you request | Cron reports, triage, recurring checks |
| Who can trigger | Users with write access | Same check on issue/PR events; schedule skips it |
Source: Claude Code docs, "Interactive and automation modes".
Here's a bounded nightly example, adapted from the official daily report. The concurrency, timeout and turn cap are example additions:
name: Daily Report
on:
schedule:
- cron: "0 9 * * *"
concurrency:
group: claude-daily-report
cancel-in-progress: true
jobs:
report:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
issues: read
id-token: write
steps:
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: "Generate a summary of yesterday's commits and open issues"
claude_args: |
--max-turns 6
--allowedTools "mcp__github__list_commits,mcp__github__list_issues"
Scheduling has some catches. GitHub runs scheduled workflows "only from the default branch," and in public repos it disables the schedule after 60 days without repository activity (Claude Code docs). Scheduled runs are attributed to whoever last changed the cron line. If that user is a bot, the human-actor check rejects the run unless the bot is in allowed_bots.
Loops are the other trap. If your workflow posts a comment containing @claude using GITHUB_TOKEN, nothing triggers, because "The github-actions user cannot trigger subsequent GitHub Actions workflows" (action FAQ). That's GitHub preventing infinite loops on purpose, so keep it. If you need chaining, use your own app token rather than widening allowed_bots to '*'. For machine-side scheduling outside GitHub, see our cron job setup guide.
Is it safe to run Claude Code on pull requests from forks or untrusted users?
Only with tight limits, because PR bodies, issues and comments can carry prompt injection. The defaults help: fork runs get no secrets, and non-write users are blocked.
GitHub's rule: "With the exception of GITHUB_TOKEN, secrets are not passed to the runner when a workflow is triggered from a forked repository" (GitHub Docs). That's why a plain pull_request review simply doesn't run on forks. Switching to pull_request_target to get fork coverage changes the risk. The security doc warns that pull_request_target and workflow_run "execute with the base repository's secrets" (security.md).
| Setting | Risk | Safer choice |
|---|---|---|
pull_request (default) | Low; no secrets on forks | Keep for review |
pull_request_target + checkout PR head into root | High; untrusted code runs with your secrets | Check out the head into a subfolder (path: pr-head) and pass --add-dir pr-head |
allowed_non_write_users: "*" | High; bypasses the main access check | Avoid, or pair with issues: write only and Bash(gh issue view:*) |
allowed_bots: "*" on a public repo | External apps may trigger runs | List bot names explicitly |
Personal access token as github_token | Static token can leak via injection | Use ${{ secrets.GITHUB_TOKEN }}, which rotates per run |
Source: security.md.
The core rule is short: "Do not check out an untrusted ref into the workspace root before this action" (security.md). On PRs, the action restores Claude config paths like .claude/, .mcp.json and CLAUDE.md from the base branch. The security doc notes that files such as package.json, lockfiles and Makefile stay at the PR head. Our inference from that is that an allowed command like npm test, which runs scripts defined in package.json, would execute whatever the PR author wrote there.
The action strips HTML comments, invisible characters and hidden attributes before Claude sees them, though the doc admits "new bypass techniques may emerge." On public repos, include_comments_by_actor lets you allowlist whose comments reach Claude. For more rule ideas, see our guardrails examples.
Should you use an Anthropic API key or OIDC (Bedrock/Vertex) for Claude Code GitHub Actions?
An Anthropic API key secret is the simplest choice for solo builders. OIDC through Bedrock, Vertex, Foundry or Anthropic workload identity federation avoids long-lived keys but needs cloud-side setup.
| Method | Secret stored | Extra setup | Suits | Doesn't suit |
|---|---|---|---|---|
ANTHROPIC_API_KEY | Long-lived key | None beyond the secret | Solo builders, small teams | Orgs that ban static keys |
CLAUDE_CODE_OAUTH_TOKEN | Subscription token | claude setup-token | Pro/Max users who want plan billing | Org-wide shared secrets |
| Anthropic workload identity federation | None (IDs only) | Console federation rule and service account; id-token: write | Teams on the Claude API wanting no keys | Quick experiments |
| Bedrock / Agent Platform / Foundry | None static; cloud OIDC role | Cloud role, custom GitHub App with APP_ID, APP_PRIVATE_KEY | Companies billing through a cloud account | Anyone without cloud admin access |
Sources: Claude Code docs and cloud providers page.
For federation, set anthropic_federation_rule_id (fdrl_...) and anthropic_organization_id. You can optionally add anthropic_service_account_id (svac_...) and anthropic_workspace_id. The docs say the action needs id-token: write for that exchange "even when you pass your own github_token" (Claude Code docs). For cloud providers, set use_bedrock: "true", use_vertex: "true" or use_foundry: "true" and follow the provider page. Bedrock setups typically store an AWS_ROLE_TO_ASSUME secret, which is a role ARN, not a key.
For a wider comparison of auth and billing when Claude runs headless, see our headless mode guide.
How do you cap cost and runaway runs in Claude Code GitHub Actions?
Cap Claude Code GitHub Actions cost with --max-turns, job timeout-minutes, concurrency groups and narrow triggers, plus a cheaper model for routine work. Each run uses both GitHub Actions minutes and Claude tokens (Claude Code docs).
The default ceiling is generous. GitHub's job timeout is "The maximum number of minutes to let a job run before GitHub automatically cancels it. Default: 360" (GitHub workflow syntax). That's six hours of runner time if a run wanders. Setting timeout-minutes: 10 to 20 on review and report jobs is a reasonable starting point (example values; tune to your repo).
| Control | Where | What it stops |
|---|---|---|
--max-turns N | claude_args | Long agent loops within one run |
timeout-minutes | Job | Runner minutes burning after a hang |
concurrency + cancel-in-progress: true | Workflow or job | Stacked runs on rapid pushes |
paths: / types: filters | on: | Runs on docs-only or irrelevant changes |
if: contains(github.event.comment.body, '@claude') | Job | Runner start on every comment |
--model | claude_args | Paying top-tier rates for simple tasks |
The concurrency option means "cancel any currently running job or workflow in the same concurrency group" (GitHub workflow syntax). For PR review, group by PR number: group: review-${{ github.event.pull_request.number }}.
On pricing, API runs bill per token at the rates on Claude's pricing page. With an OAuth token, runs draw on your subscription instead, and current plan prices are listed on the same pricing page. Subscription use is still subject to plan usage limits, so a busy repo may hit them. To see where tokens go, read our token usage guide.
Why is Claude Code failing with permission errors in GitHub Actions?
Most Claude Code GitHub Actions permission errors come from one of five causes. Either a scope is missing from permissions:, the Claude GitHub App isn't installed, the trigger user lacks write access, a tool isn't in --allowedTools, or a github_token was passed by mistake.
| Symptom | Likely cause | Fix |
|---|---|---|
| OIDC authentication error | No id-token: write | Add it to the job's permissions: (FAQ) |
| "You don't have permission" style rejection | Commenter lacks write access | Grant write, or use allowed_non_write_users with care |
Nothing happens on @claude | App not installed, workflows disabled, or /claude instead of @claude | Check each in the docs' troubleshooting list |
403 Resource not accessible by integration | App token can't call the /user endpoint | See the FAQ entry; often caused by custom steps calling /user |
| Claude says it can't run a command | Bash not allowed | Add Bash(cmd:*) to --allowedTools |
| Review finishes but no comments | Missing --comment or inline-comment tool | Add both, per the review example |
| CI doesn't run on Claude's commits | github_token: ${{ secrets.GITHUB_TOKEN }} passed | Remove it so the action uses the app, or use a custom app token |
Comments not from claude[bot] | github_token provided | Remove it unless using a custom app |
| Scheduled run rejected | Last cron editor is a bot | Add that bot to allowed_bots |
| Claude won't edit workflow files | App lacks workflow write via the action | Edit .github/workflows/ yourself |
Sources: Claude Code troubleshooting and action FAQ.
A quick diagnostic order:
- Open the failed run under Actions and find the first red step.
- If the error mentions OIDC or token exchange, check
permissions:first. - If Claude ran but did nothing useful, rerun with
--debuginclaude_args(private repos only, given public logs). - Test the API key locally with
claudebefore debugging YAML further, as the docs suggest.
Next steps
If maintaining workflow files isn't the route you want, there are two alternatives.
Anthropic's Code Review runs automatic PR reviews without YAML. It suits teams that only want review. It doesn't suit you if you need cron jobs, issue-to-PR automation or custom tool lists. It also requires the official Claude GitHub App with its full permission set (Claude Code docs). If cost predictability matters more than per-token billing, a Claude Pro or Max subscription with CLAUDE_CODE_OAUTH_TOKEN is another route. Plans are listed on Claude's pricing page. It's a poor fit for org-wide shared secrets.
Related guides on this site:
- Claude Code Permissions Best Practices: Headless Template
- Claude Code Subagent allowed-tools: Syntax, Examples, Fixes
- AI Agent Guardrails Example: Copy-Paste Claude Code Rules
- AI Agent Monitoring in Claude Code: Catch Silent Failures
FAQ
Does Claude Code GitHub Actions need id-token: write?
Yes, with the default Claude GitHub App authentication. The action uses the workflow's OIDC token to get its app token, and the action FAQ says you "must add the id-token: write permission." Anthropic workload identity federation needs it too, even if you pass your own github_token (Claude Code docs).
Can Claude Code push commits or open PRs from a workflow?
It can push commits to a branch (prefix claude/ by default) when the job has write scopes. But "Claude doesn't create PRs by default." It posts a link to a pre-filled PR page so a human opens the PR and branch protection still applies (action FAQ).
Should I pin the claude-code-action to a commit SHA?
The official examples use the moving @v1 tag, which picks up fixes automatically. You can see it in examples/claude.yml and the workflows in the Claude Code docs. GitHub's security hardening guide says pinning an action to a full-length commit SHA is the only way to use it as an immutable release. That makes a SHA the safer choice for workflows that hold write scopes or secrets. The trade-off is that you have to bump it yourself. By the same guidance, a branch ref like @main changes with every commit, so avoid it in production workflows.
Can I use Claude Code in GitHub Actions without an API key?
Yes. You can use a CLAUDE_CODE_OAUTH_TOKEN from claude setup-token on a Claude subscription. You can also use Anthropic workload identity federation, or OIDC with Amazon Bedrock, Google Cloud's Agent Platform or Microsoft Foundry (Claude Code docs). Each option still needs the Claude GitHub App or a custom app for GitHub access.
Sources
- Claude Code GitHub Actions (official docs)
- Use Claude Code GitHub Actions with cloud providers
- Claude Code authentication: long-lived token
- Claude Code Review
- anthropics/claude-code-action: security.md
- anthropics/claude-code-action: faq.md
- anthropics/claude-code-action: usage.md
- anthropics/claude-code-action: examples/claude.yml
- GitHub Docs: Controlling permissions for GITHUB_TOKEN
- GitHub Docs: Workflow syntax
- GitHub Docs: Using secrets in GitHub Actions
- GitHub Docs: Security hardening for GitHub Actions
- Claude pricing
Spotted something out of date or wrong? Tell us at shigoto.ai.note@gmail.com. Guides are re-checked against official sources on a rolling schedule.