UnattendedClaude Code Guides

Claude Code GitHub Actions Permissions: Least-Privilege YAML

By Aoba Works · Published · Sources checked · 19 min read

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.

Which option is right for you?
Your situationWhat to pick firstLimitation / not forFirst page to open
Beginner, one repo, already use Claude Code locallyRun /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 elseCode-review skill workflow with read-only token scopesComments 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 findsRun a skill example
Daily work: @claude fixes issues and pushes commitsInteractive 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 commentexamples/claude.yml
Recurring report or triage on a cronAutomation mode with a prompt and named MCP toolsRuns 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 requestsRun on a schedule
Tight budget, already pay for Pro/MaxCLAUDE_CODE_OAUTH_TOKEN from claude setup-tokenTied to the subscription of whoever generated it and subject to plan usage limits. Not for org-wide shared secretsAuthentication docs
Company with an AWS/GCP/Azure account and a no-static-keys ruleBedrock, Vertex or Foundry via OIDCNeeds a cloud role plus a custom GitHub App. Not for quick experiments or anyone without cloud admin accessCloud 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.

What permissions does Claude Code GitHub Actions need?
LayerWhere you set itWhat it controlsMinimum for this action
Claude GitHub Appgithub.com/apps/claude install screenWhat the app token can do in the repoContents, Issues, Pull requests: read and write
Workflow permissions:.github/workflows/*.ymlGITHUB_TOKEN scopes and the OIDC tokenid-token: write plus the read scopes your job needs
claude_args --allowedToolswith: input of the action stepWhich tools Claude may callOnly 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.

How do you set a least-privilege permissions block for Claude Code?
Job typepermissions: block (from official examples)Why
PR review via code-review skillcontents: read, pull-requests: read, issues: read, id-token: writeThe app token posts the inline comments, so the workflow token only reads
Scheduled reportcontents: read, issues: read, id-token: writeReads 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: readPushes commits and updates comments; actions: read lets Claude see CI results
Issue labeling for non-write usersissues: write only, with github_token passedThe 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:

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.

How do you use allowed_tools and claude_args in Claude Code GitHub Actions?
TaskExample --allowedTools valueNotes
Post inline review commentsmcp__github_inline_comment__create_inline_commentRequired even if the skill already lists it; the MCP server only starts when this flag names it
Read commits and issues for a reportmcp__github__list_commits,mcp__github__list_issuesFrom the official daily-report example
Run tests and lintBash(npm test:),Bash(npm run lint:)Example; narrower than the FAQ's Bash(npm:*)
Read an issue for triageBash(gh issue view:*)The security doc's recommended scope for non-write users
Edit filesEdit,Read,WriteFrom 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:

  1. **Bash(npm:*) is wider than it looks.** It covers npm install and npm publish as well as npm test. Name subcommands when you can. Our allowed-tools syntax guide covers how the pattern matching works.
  2. Review posts nothing. Either the --comment argument is missing from the review prompt, or claude_args doesn't name the inline-comment MCP tool. Without --comment, "Claude posts nothing" and findings stay in the run log (Claude Code docs).
  3. Still using @beta keys. direct_prompt, mode, max_turns and model are beta-era inputs. Move them to prompt and claude_args. The custom_instructions input 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):

  1. Open the repo and click Settings (or the More dropdown if Settings isn't shown).
  2. In the sidebar's Security section, click Secrets and variables, then Actions.
  3. On the Secrets tab, click New repository secret.
  4. Enter ANTHROPIC_API_KEY as Name, paste the key from the Claude Console as Secret, and click Add secret.
  5. In the workflow, add anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} under with:.

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:

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:

  1. Install the Claude GitHub App on the repo.
  2. Add the ANTHROPIC_API_KEY secret (previous section).
  3. Save the file as .github/workflows/claude-code-review.yml on a branch and open a PR. That PR should trigger the first review.
  4. 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.

How do you run Claude Code on a schedule or on issues without babysitting?
Interactive modeAutomation mode
How it startsNo prompt input; waits for @claudeprompt input set; runs on the event
Where results goComment on the issue or PRWorkflow run log, unless the prompt and a tool let it post
Good forAd-hoc fixes you requestCron reports, triage, recurring checks
Who can triggerUsers with write accessSame 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).

Is it safe to run Claude Code on pull requests from forks or untrusted users?
SettingRiskSafer choice
pull_request (default)Low; no secrets on forksKeep for review
pull_request_target + checkout PR head into rootHigh; untrusted code runs with your secretsCheck out the head into a subfolder (path: pr-head) and pass --add-dir pr-head
allowed_non_write_users: "*"High; bypasses the main access checkAvoid, or pair with issues: write only and Bash(gh issue view:*)
allowed_bots: "*" on a public repoExternal apps may trigger runsList bot names explicitly
Personal access token as github_tokenStatic token can leak via injectionUse ${{ 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.

Should you use an Anthropic API key or OIDC (Bedrock/Vertex) for Claude Code GitHub Actions?
MethodSecret storedExtra setupSuitsDoesn't suit
ANTHROPIC_API_KEYLong-lived keyNone beyond the secretSolo builders, small teamsOrgs that ban static keys
CLAUDE_CODE_OAUTH_TOKENSubscription tokenclaude setup-tokenPro/Max users who want plan billingOrg-wide shared secrets
Anthropic workload identity federationNone (IDs only)Console federation rule and service account; id-token: writeTeams on the Claude API wanting no keysQuick experiments
Bedrock / Agent Platform / FoundryNone static; cloud OIDC roleCloud role, custom GitHub App with APP_ID, APP_PRIVATE_KEYCompanies billing through a cloud accountAnyone 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).

How do you cap cost and runaway runs in Claude Code GitHub Actions?
ControlWhereWhat it stops
--max-turns Nclaude_argsLong agent loops within one run
timeout-minutesJobRunner minutes burning after a hang
concurrency + cancel-in-progress: trueWorkflow or jobStacked runs on rapid pushes
paths: / types: filterson:Runs on docs-only or irrelevant changes
if: contains(github.event.comment.body, '@claude')JobRunner start on every comment
--modelclaude_argsPaying 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.

Why is Claude Code failing with permission errors in GitHub Actions?
SymptomLikely causeFix
OIDC authentication errorNo id-token: writeAdd it to the job's permissions: (FAQ)
"You don't have permission" style rejectionCommenter lacks write accessGrant write, or use allowed_non_write_users with care
Nothing happens on @claudeApp not installed, workflows disabled, or /claude instead of @claudeCheck each in the docs' troubleshooting list
403 Resource not accessible by integrationApp token can't call the /user endpointSee the FAQ entry; often caused by custom steps calling /user
Claude says it can't run a commandBash not allowedAdd Bash(cmd:*) to --allowedTools
Review finishes but no commentsMissing --comment or inline-comment toolAdd both, per the review example
CI doesn't run on Claude's commitsgithub_token: ${{ secrets.GITHUB_TOKEN }} passedRemove it so the action uses the app, or use a custom app token
Comments not from claude[bot]github_token providedRemove it unless using a custom app
Scheduled run rejectedLast cron editor is a botAdd that bot to allowed_bots
Claude won't edit workflow filesApp lacks workflow write via the actionEdit .github/workflows/ yourself

Sources: Claude Code troubleshooting and action FAQ.

A quick diagnostic order:

  1. Open the failed run under Actions and find the first red step.
  2. If the error mentions OIDC or token exchange, check permissions: first.
  3. If Claude ran but did nothing useful, rerun with --debug in claude_args (private repos only, given public logs).
  4. Test the API key locally with claude before 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:

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

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.

How this guide was made: drafted with AI assistance from the official sources linked below, then passed an automated editorial check before publishing. Our process