# anthropics/claude-code-base-action: running Claude Code inside GitHub Actions

> A thin GitHub Action wrapper that installs and runs the Claude Code CLI in a workflow step, with direct prompts, prompt files, custom system prompts and Workload Identity Federation for authentication. It enforces no trust boundaries on its own, so processing untrusted input through it is the wrong use.

**anthropics/claude-code-base-action** — This repo is a mirror of the contents of base-action in https://github.com/anthropics/claude-code-action.

- Repository: https://github.com/anthropics/claude-code-base-action
- Stars: 1,015 · Forks: 603
- Language: TypeScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/anthropics-claude-code-base-action

## What the base action is, and what it is not

The repository is a mirror. Its README opens with a warning that it is an automated mirror of the `base-action` directory from `anthropics/claude-code-action`, and instructs readers not to submit pull requests or issues there, pointing instead to the main repository's issue tracker, pull request page and documentation. A `MIRROR_DISCLAIMER.md` file sits at the repository root alongside `CONTRIBUTING.md` and `CODE_OF_CONDUCT.md`.

So the `base-action` directory of a larger action is mirrored into its own repository, which can then be referenced as a step. That is worth knowing before you file anything.

What the action does is narrow. The README describes it as a thin wrapper that installs and runs Claude Code with the inputs you provide, and it says you can build any custom workflow on top of Claude Code. If all you want is the ability to tag `@claude` in an issue or pull request and have it handled, the README redirects you to the Claude Code action and GitHub app instead, which is the supported path for that scenario.

The primary language is TypeScript. The `src/` directory holds the implementation, `action.yml` defines the action entry point, and `package.json` lists `@actions/core`, the `@anthropic-ai/claude-agent-sdk` at version `^0.3.301`, and `shell-quote` as runtime dependencies.

## The trust model: the action enforces nothing for you

This is the section to read twice, because the security posture of the action is entirely the caller's problem.

The README states that the action does not enforce any trust boundaries on its own, and that running it in a directory is equivalent to running Claude Code in that directory. Claude reads project-level configuration from the working directory, and the list is specific: `.claude/`, `CLAUDE.md`, `.mcp.json` and similar. The action's own setup steps run from there as well.

The consequence is direct. If a repository contains a `.mcp.json` that registers an MCP server, and your workflow runs this action in that repository, the agent will load that server definition. The caller is responsible for ensuring the working directory and the prompt are trusted.

The README then names the case where you should not use this action: workflows that process untrusted input, namely issues, fork pull requests and external comments. For those it points at `anthropics/claude-code-action`, which provides actor permission checks and restores project configuration from the base ref in pull request contexts, and which it calls the supported path for those scenarios. The README also links to Claude Code's security documentation and to the GitHub Actions guidance on `pull_request_target` for background on why a `pull_request_target` workflow holding a privileged token should not check out and run attacker-controlled code.

The design choice here is that the action stays small and predictable, and the separation of duties is explicit. Whether that is a feature or a trap depends entirely on whether your workflow inputs are trusted.

## Three ways to supply the prompt

The `with` block of the action accepts a prompt in several forms, and the README shows each of them.

The first is a direct prompt, where the text sits in the workflow file:

```yaml
- name: Run Claude Code with direct prompt
  uses: anthropics/claude-code-base-action@beta
  with:
    prompt: "Your prompt here"
    claude_args: '--allowedTools "Bash(git:*),Read,Glob,Grep"'
    anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```

The second is a prompt file, which keeps long instructions out of the YAML:

```yaml
- name: Run Claude Code with prompt file
  uses: anthropics/claude-code-base-action@beta
  with:
    prompt_file: "/path/to/prompt.txt"
    claude_args: '--allowedTools "Bash(git:*),Read,Glob,Grep"'
    anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```

The third variant is not a different input but a different `claude_args` value, since the arguments are passed to the Claude CLI directly. This one adds `--max-turns 5` to cap the conversation, which matters for a CI job with a wall clock limit and a bill attached:

```yaml
- name: Run Claude Code with limited turns
  uses: anthropics/claude-code-base-action@beta
  with:
    prompt: "Your prompt here"
    claude_args: |
      --allowedTools "Bash(git:*),Read,Glob,Grep"
      --max-turns 5
    anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```

Note the same `--allowedTools` list on every example. The tool set is expressed as CLI arguments rather than as action inputs, so the actual capability grant lives in the string you pass to `claude_args`.

## System prompt, appended system prompt and environment settings

The action exposes the CLI's own prompt controls, and the difference between the two variants is worth being precise about.

`--system-prompt` replaces the default system prompt. The README's example is a backend engineer persona with a list of focus areas:

```yaml
- name: Run Claude Code with custom system prompt
  uses: anthropics/claude-code-base-action@beta
  with:
    prompt: "Build a REST API"
    claude_args: |
      --system-prompt "You are a senior backend engineer. Focus on security, performance, and maintainability."
      --allowedTools "Bash(git:*),Read,Glob,Grep"
    anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```

`--append-system-prompt` keeps the default and adds to it, in the example a request to code review the work after writing it.

Separately from `claude_args`, there is a `settings` input that takes Claude Code settings as a JSON string or as a path to a settings JSON file, and the `env` block inside it is how you pass custom environment variables to the run:

```yaml
- name: Run Claude Code with custom environment variables
  uses: anthropics/claude-code-base-action@beta
  with:
    prompt: "Deploy to staging environment"
    settings: |
      {
        "env": {
          "ENVIRONMENT": "staging",
          "API_URL": "https://api-staging.example.com",
          "DEBUG": "true"
        }
      }
    claude_args: '--allowedTools "Bash(git:*),Read,Glob,Grep"'
    anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```

Model selection and a fallback model for handling API errors are passed through `--model` and `--fallback-model` in `claude_args`. The README's fallback example names `claude-opus-4-1-20250805` with `claude-sonnet-4-20250514` as the fallback.

Because the settings JSON is a path or a string, a repository can carry a settings file, and that file is another file the agent will honour. The trust boundary discussed above covers it.

## Workload Identity Federation replaces the static key

The newest authentication path avoids a long-lived credential entirely. Instead of an API key or an OAuth token, the action can authenticate through Workload Identity Federation: it fetches the workflow's GitHub OIDC token and the Claude Code CLI exchanges it for a short-lived access token.

The job needs the `id-token: write` permission, and the step takes three federation identifiers:

```yaml
permissions:
  contents: read
  id-token: write

steps:
  - name: Run Claude Code with workload identity federation
    uses: anthropics/claude-code-base-action@beta
    with:
      prompt: "Your prompt here"
      anthropic_federation_rule_id: fdrl_xxxxxxxxxxxx
      anthropic_organization_id: 00000000-0000-0000-0000-000000000000
      anthropic_service_account_id: svac_xxxxxxxxxxxx
```

The permission block is scoped to `contents: read` plus `id-token: write`, which is a meaningful tightening compared with a job that holds a repository secret.

There is one precedence rule to get right, and the README states it as a warning: do not set `anthropic_api_key` or `claude_code_oauth_token` alongside the federation inputs, because a static credential takes precedence and federation will not be used. A misconfigured job would therefore appear to work while still using the long-lived secret, which is the failure mode to check for when auditing a workflow.

The third authentication form, an OAuth token, is passed as `claude_code_oauth_token` from `secrets.CLAUDE_CODE_OAUTH_TOKEN`, as shown in the README's final usage example.

## Version tags, the mirror caveat and release history

Every example in the README pins the step to `@beta`, and the release list shows why that is a moving target. Published tags include a `beta` release named Claude Code Base Action beta dated 2025-05-19, v0.0.62 on 2025-08-21 and v0.0.63 on 2025-08-22.

That is the whole release history as listed, and the last of the tagged releases is v0.0.63 from 2025-08-22, while the last push to this repository was on 2026-10-18. The repository is not archived, so work continues on the default branch, but the tag set is a long way behind the branch head. A workflow pinned to `@beta` picks up whatever that tag points at at run time, while a workflow pinned to `v0.0.63` gets code from 2025-08-22 and the gap between the two is months of unreviewed drift.

The `package.json` adds a third versioning layer: the package is named `@anthropic-ai/claude-code-base-action` with version `1.0.0` and is marked `private`, so it is not published to a registry and the number is not a published artifact version. The scripts section shows the development tooling: `format` and `format:check` run prettier, `test` runs `bun test`, `typecheck` runs `tsc --noEmit`, and there is a `bun.lock` file alongside `package-lock.json`.

The licence is MIT, with a `LICENSE` file at the repository root. MIT obligations are minimal, but the licence covers this mirror's contents, and contributions are directed to the main repository rather than here.

## Where the base action is the wrong tool

The clearest wrong case is documented by the project itself: any workflow that reads untrusted input. The README says the action does not enforce trust boundaries, that it reads project configuration from the working directory, and that for issues, fork pull requests and external comments you should use `anthropics/claude-code-action` instead, because that path provides actor permission checks and restores project configuration from the base ref in pull request contexts.

That distinction is not cosmetic. In a fork pull request, the repository content in the checkout is the fork's content, and it is attacker-controlled. A `.mcp.json` in that checkout, or a `CLAUDE.md` instructing the agent to exfiltrate the environment, is read by the agent. The base action has nothing to stop it.

A second wrong case is when you want the tag and comment integration. The README points that to the Claude Code action and GitHub app rather than this wrapper, so building a bespoke issue triage flow on the base action means reimplementing what that app already does.

The repository does contain an `examples/` directory with an `issue-triage.yml` example, which is worth reading carefully rather than copying: it is an example of the pattern the README warns about, and using it against real untrusted issues would need the permission checks and configuration restoration that only the main action provides.

A third limitation is quieter. Because the action is a thin wrapper that installs and runs Claude Code, the cost of a run is the cost of the model calls, and the README does not describe caching, concurrency limits or any billing control. The `--max-turns` flag in `claude_args` is the only lever shown for bounding a run.

## Conclusion

This action fits teams that want Claude Code inside their own workflows on trusted repository content, where the workflow already controls what the job can read and the prompt is written by the repository maintainers. It is the wrong choice for a workflow that reads issue bodies, fork pull requests or external comments, because the README states the action enforces no trust boundaries and the project configuration in the working directory is read as it stands. Before wiring it in, read the caller responsibility note next to the `claude_args` you pass, confirm that `anthropic_api_key` is not set alongside the federation inputs, and pin the `uses` reference to a specific release rather than the `beta` tag if you need reproducible builds.

## FAQ

### What is the difference between claude-code-base-action and the main Claude Code action?

The base action is a thin wrapper for building custom workflows on top of Claude Code. The main action adds actor permission checks and restores project configuration from the base ref in pull request contexts, which is why it is the supported path when a workflow processes untrusted input.

### How do I authenticate claude-code-base-action without an API key?

Use Workload Identity Federation. The job needs the `id-token: write` permission and the step takes `anthropic_federation_rule_id`, `anthropic_organization_id` and `anthropic_service_account_id`. Do not set `anthropic_api_key` or `claude_code_oauth_token` at the same time, since a static credential takes precedence.

### Can I limit how many turns Claude Code takes in a GitHub Actions run?

Yes. Pass `--max-turns` inside `claude_args`, which is forwarded to the Claude CLI directly. The README shows it used alongside `--allowedTools "Bash(git:*),Read,Glob,Grep"`.

### Where should I report bugs in claude-code-base-action?

Not in the mirror. The repository is an automated mirror of the `base-action` directory from `anthropics/claude-code-action`, and the README asks you to report issues and submit pull requests to the main repository instead.

### How do I pass environment variables to the Claude Code run?

Use the `settings` input, which takes Claude Code settings as a JSON string or a path to a settings JSON file, and put your variables in its `env` block. The `claude_args` input is separate and is for CLI arguments.

## Sources

- [anthropics/claude-code-base-action on GitHub](https://github.com/anthropics/claude-code-base-action)
- [Issues](https://github.com/anthropics/claude-code-base-action/issues)
- [License: MIT](https://github.com/anthropics/claude-code-base-action/blob/main/LICENSE)
- [README](https://github.com/anthropics/claude-code-base-action/blob/main/README.md)
- [Releases](https://github.com/anthropics/claude-code-base-action/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/anthropics-claude-code-base-action
