Claude Code in GitHub Actions: the safe patterns

An agent in CI is genuinely useful and is also where a prompt-injection problem becomes a supply-chain problem. The difference is entirely in which trigger you chose and what the token can do.

By the Continuum team. We build a workbench that runs Claude Code, Codex, and their peers, so the model rates quoted here are the ones our own cost analytics ship with.

The short version

The supported path is anthropics/claude-code-action@v1, installed with /install-github-app from a local Claude Code session. Authenticate with an ANTHROPIC_API_KEY secret, a CLAUDE_CODE_OAUTH_TOKEN from a subscription, or OIDC workload identity federation. Give the job a timeout, cap turns, and never point an agent at a diff from a pull_request_target trigger.

What you need to know
  • The official action is anthropics/claude-code-action@v1. @beta is retired.
  • A workflow with a prompt input runs automatically; without one it waits for @claude.
  • Subscriptions do work in CI through CLAUDE_CODE_OAUTH_TOKEN, but it is tied to one person.
  • Never pull_request_target with an agent. This is the whole security section.
  • Two access checks run before Claude starts: repository write access, and not a bot.

Two ways to run it, and when each is right

ApproachUse whenCost of it
anthropics/claude-code-action@v1You want @claude mentions, PR comments, commits pushed backA GitHub App install and its permission set
npm install -g @anthropic-ai/claude-code then claude -pYou want a bounded, read-only job and full control of every flagYou write the plumbing yourself
The separate Code Review productYou want automatic PR review with no workflow file at allLess control over prompt, model, and triggers

Most teams should start with the action. The raw CLI is the right call for one specific shape: a job that reads a diff, emits a verdict, and is not allowed to touch anything.

Setting up the official action

01

Run the installer from a local session

gh auth login        # the installer checks for the GitHub CLI
claude               # in the repository you want to connect
# then, inside the session:
/install-github-app

It installs the Claude GitHub App, stores your credential as a repository secret, pushes a branch with the workflow files you select, and opens a pull request ready to create. You need admin access on the repository.

02

Or do it by hand

Install the Claude GitHub App, add the secret yourself, and copy examples/claude.yml from the action repository into .github/workflows/. Use this path when you do not run Claude Code locally or you want to own the workflow files outright.

03

Pick an authentication method

# an API key from the Claude Console
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}

# or a subscription token, generated locally with: claude setup-token
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
  • To avoid a long-lived secret entirely, use OIDC workload identity federation: set anthropic_federation_rule_id and anthropic_organization_id, and grant the job id-token: write.
  • Rolling out across an organisation: install the app once at organisation level, store the secret as an organisation Actions secret, and define the job once as a reusable workflow.

Interactive mode, automation mode, and who may trigger a run

The action decides how to run from your configuration, and the difference is one input.

InteractiveAutomation
ConfigurationNo prompt inputA prompt input is set
Starts onThe trigger phrase, @claude by defaultAny GitHub event, including schedule
Results appearAs a comment on the issue or PRIn the workflow run log
Typical useAsk Claude to implement or explain somethingReview, triage, scheduled reports
.github/workflows/claude.yml - interactive mode.
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: 20
    permissions:
      contents: write
      pull-requests: write
      issues: write
      id-token: write        # required for the action's GitHub App auth
      actions: read          # lets Claude read CI results on the PR
    steps:
      - uses: actions/checkout@v6
        with: { fetch-depth: 1 }

      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          claude_args: |
            --max-turns 15
            --model claude-sonnet-5

The trigger that will get you compromised

The difference is not cosmetic.

TriggerRuns code fromHas secrets?Safe with an agent?
pull_requestThe PR branchNo, for forksYes
pull_request_targetThe base branchYesNo

The defence is capability, not prompting. An agent with read-only tools and a token that cannot merge cannot act on an instruction hidden in a diff, whatever that instruction says. Do not attempt to solve this with a system prompt telling the model to ignore instructions in code it reads: that is a request, and the whole problem is that requests are not boundaries.

  • On public repositories GitHub withholds secrets from fork-triggered runs entirely, so a review workflow on pull_request runs only for branches in the same repository. That is the correct behaviour, not a bug to work around.
  • Give the reviewer job contents: read and pull-requests: read, and let a separate step post the comment.
  • Restrict tools explicitly with --allowedTools "Read,Grep,Glob" in claude_args. Default permissions are designed for a human sitting there.
  • Pass untrusted text through an environment variable and a file rather than interpolating it into a shell command, which also closes the ordinary shell-injection hole that exists here independently of the agent.

A bounded read-only job with the raw CLI

When you want a verdict rather than a collaborator, the CLI is smaller and every knob is visible.

Read-only review, pinned version, hard timeout, cost logged.
name: agent-review
on:
  pull_request:                 # NOT pull_request_target

permissions:
  contents: read
  pull-requests: write

concurrency:
  group: agent-review-${{ github.ref }}
  cancel-in-progress: true

jobs:
  review:
    if: github.event.pull_request.draft == false
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v6
        with: { fetch-depth: 0 }

      - run: npm install -g @anthropic-ai/claude-code@2.1.219

      - name: Review
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          DISABLE_AUTOUPDATER: "1"
        run: |
          git diff origin/${{ github.base_ref }}...HEAD > diff.patch

          claude -p "Review this diff. Report only defects that will actually
                     occur, each with the triggering input. No style comments.
                     If nothing is wrong, say so in one line." \
            --allowedTools "Read,Grep,Glob" \
            --model claude-sonnet-5 \
            --output-format json < diff.patch > out.json

          jq -r '.result' out.json > review.md
          echo "cost: \$$(jq -r '.total_cost_usd' out.json)" >> $GITHUB_STEP_SUMMARY

      - name: Comment
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: gh pr comment ${{ github.event.number }} --body-file review.md

Keeping the bill bounded

Every run consumes two things: GitHub Actions minutes on the runner, and tokens against your key or subscription. Both are unattended, which is exactly why they run away.

ControlWhereWhy
timeout-minutesThe jobA stuck agent bills until something kills it
--max-turnsclaude_argsCaps how much work a single run can do
--model claude-sonnet-5claude_argsWithout it you inherit the default model
Console spend limitClaude Console workspaceThe only hard ceiling that exists
concurrency groupThe workflowStops ten pushes queueing ten agents
Skip drafts and botsThe if: conditionMost draft pushes do not need a review
Log total_cost_usdThe JSON resultPer-run cost for free, from output you already have

The two failures people actually hit.

SymptomCause
CI does not run on Claude commitsYou passed github_token: secrets.GITHUB_TOKEN. GitHub never triggers workflows on commits made with the default token. Remove it so the action authenticates as the app
@claude does nothingApp not installed, workflows disabled, the comment says @claude-bot, or the commenter has no write access

Questions people ask

Run /install-github-app from a local Claude Code session in the repository. It installs the Claude GitHub App, stores your credential as a repository secret, and opens a pull request with the workflow files. You need admin access on the repository and the GitHub CLI authenticated.

Yes. Generate a token locally with claude setup-token and store it as CLAUDE_CODE_OAUTH_TOKEN, then pass it to the claude_code_oauth_token input. It works on Pro, Max, Team, and Enterprise plans. The token is tied to one person subscription, so use a Console API key for anything shared across repositories.

With the pull_request trigger and read-only tools, yes: GitHub withholds secrets from fork-triggered runs on public repositories. With pull_request_target it is not, because that runs with your secrets against a diff an outsider controls.

By capability, not prompting. Read-only tools, a token that cannot merge, and never pull_request_target. An injected instruction is unactionable regardless of what it says when the agent has no tool that could act on it.

Check the app is installed, workflows are enabled, the secret is set, the comment contains @claude as a complete word rather than /claude or @claude-bot, and the commenting user has write access to the repository.

GitHub does not trigger workflows on commits made with the default GITHUB_TOKEN. If you passed github_token: secrets.GITHUB_TOKEN to the action, remove it so it authenticates as the Claude GitHub App, or pass a custom app token instead.

Typically a few cents to a few tens of cents on Sonnet for a normal diff. The JSON output reports total_cost_usd per run, so log it to the step summary and measure yours rather than estimating.

Change @beta to @v1, remove the mode input because the action now detects the mode from whether you set a prompt, replace direct_prompt with prompt, and move CLI options such as max_turns and model into claude_args.

Sources

Every figure above was read from these pages on August 2026. Vendors reprice without notice; if you find a stale number, tell us.

  1. Claude Code GitHub Actions
  2. anthropics/claude-code-action on GitHub
  3. Claude Code: manage costs effectively
Try it

CI spend,
next to the rest.

Continuum aggregates agent cost across interactive and automated runs, so a runaway job is visible in days.

free app · your subscriptions · local-first