### Documentation

# Use Claude Code Action with Aperture

Last validated: Jun 16, 2026 | Copy as Markdown | [View as Markdown](/content/docs/aperture/how-to/use-claude-code-action.md)

Aperture by Tailscale is currently [in beta](/content/docs/reference/tailscale-release-stages#beta/index.html).

Configure the [Claude Code GitHub Action](https://github.com/anthropics/claude-code-action) to send requests through [Aperture by Tailscale](/content/docs/aperture/index.html) so that CI runners use their Tailscale identity for authentication instead of per-repository API keys.

## [Prerequisites](/content/docs/aperture/how-to/use-claude-code-action#prerequisites/index.html)

- An [Aperture instance](/content/docs/aperture/get-started/index.html) with an Anthropic provider configured and at least one Anthropic model enabled.
- A Tailscale account with [Owner, Admin, or Network admin](/content/docs/reference/user-roles/index.html) permissions.
- A GitHub repository with GitHub Actions enabled and admin access to create repository secrets.

To avoid unexpected TLS issues, use `http://` for the Aperture URL when configuring LLM clients. All connections remain encrypted using WireGuard, even when HTTPS is not used.

## [Create a CI tag and grant access to Aperture](/content/docs/aperture/how-to/use-claude-code-action#create-a-ci-tag-and-grant-access-to-aperture/index.html)

CI runners need a Tailscale tag, network access to the Aperture device, and a grant that authorizes model usage.

1. Open the [Access controls](https://console.tailscale.com/admin/acls/file) page in the admin console.
2. In the `tagOwners` section, add `tag:ci` if it does not already exist:

```json
    "tagOwners": {
         "tag:ci": []
    }
    ```

An empty owner list means [Owners, Admins, and Network admins](/content/docs/reference/user-roles/index.html) can apply the tag.
3. Add a grant that gives `tag:ci` devices network access to the Aperture device and permission to use Anthropic models:

```json
    {
         "grants": [
           {
             "src": ["tag:ci"],
             "dst": ["<aperture-hostname>"],
             "ip": ["tcp:80"],
             "app": {
               "tailscale.com/cap/aperture": [
                 { "role": "user" },
                 { "models": "anthropic/**" }
               ]
             }
           }
         ]
    }
    ```

Replace `"<aperture-hostname>"` with the hostname or Tailscale IP address of your Aperture device if it differs from the default. Adjust the `models` pattern to restrict CI runners to specific models, such as `anthropic/claude-sonnet-4-6`.
4. Save the tailnet policy file. The rules take effect immediately.

The grant must include both a `role` entry and a `models` entry. Without `role`, Aperture returns HTTP 403 even if the `models` pattern matches.

For more information on grant syntax and model patterns, refer to [grant access to models](/content/docs/aperture/how-to/grant-model-access/index.html). Because this grant lives in the tailnet policy file, it includes an explicit `dst` field. For how grant requirements differ between the tailnet policy file and the Aperture configuration, refer to [Aperture grants versus tailnet policy file grants](/content/docs/aperture/reference/aperture-vs-tailnet-grants/index.html).

## [Set up Tailscale authentication for GitHub Actions](/content/docs/aperture/how-to/use-claude-code-action#set-up-tailscale-authentication-for-github-actions/index.html)

The [Tailscale GitHub Action](/content/docs/integrations/github/github-action/index.html) needs credentials to join your tailnet as an ephemeral node.

### [Workload identity federation (recommended)](/content/docs/aperture/how-to/use-claude-code-action#workload-identity-federation-recommended/index.html)

Workload identity federation uses GitHub's OIDC tokens to authenticate, eliminating secrets that can expire or leak.

1. Create a [federated identity](/content/docs/features/workload-identity-federation/index.html) in the Tailscale admin console. Set the issuer to GitHub Actions and configure the subject claim to match your repository. Assign the `auth_keys` scope and the `tag:ci` tag.
2. Copy the **Client ID** and **Audience** values.
3. In your GitHub repository, create two [repository secrets](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets):
   - `TS_OAUTH_CLIENT_ID`: your federated identity Client ID.
   - `TS_AUDIENCE`: your federated identity Audience.

Workload identity federation requires the `id-token: write` permission in your GitHub Actions workflow, which you add when you configure the workflow file.

### [OAuth client](/content/docs/aperture/how-to/use-claude-code-action#oauth-client/index.html)

If your organization does not use workload identity federation, create an [OAuth client](/content/docs/features/oauth-clients/index.html) instead.

1. Create an [OAuth client](/content/docs/features/oauth-clients/index.html) in the admin console with the `auth_keys` scope and the `tag:ci` tag.
2. Copy the **Client ID** and **Client secret**.
3. In your GitHub repository, create two repository secrets:
   - `TS_OAUTH_CLIENT_ID`: your OAuth Client ID.
   - `TS_OAUTH_SECRET`: your OAuth Client secret.

For detailed instructions on either option, refer to the [Tailscale GitHub Action documentation](/content/docs/integrations/github/github-action/index.html).

## [Add Claude Code Action to the workflow](/content/docs/aperture/how-to/use-claude-code-action#add-claude-code-action-to-the-workflow/index.html)

Create or update a workflow file (for example, `.github/workflows/claude.yml`) with the following content. This example uses workload identity federation. If you use an OAuth client, replace `audience` with `oauth-secret` and reference `secrets.TS_OAUTH_SECRET`.

```yaml
name: Claude Code
on:
  pull_request:

permissions:
  id-token: write
  pull-requests: write
  contents: read

jobs:
  claude:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

- name: Connect to tailnet
        uses: tailscale/github-action@v4
        with:
          oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
          audience: ${{ secrets.TS_AUDIENCE }}
          tags: tag:ci
          ping: ai

- name: Run Claude Code
        uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: "-"
        env:
          ANTHROPIC_BASE_URL: "http://ai"
          CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
```

### [Key settings](/content/docs/aperture/how-to/use-claude-code-action#key-settings/index.html)

| Setting | Purpose |
| --- | --- |
| `tags: tag:ci` | Assigns the tag that Aperture uses to identify and authorize the caller. |
| `ping: <aperture-hostname>` | Waits for connectivity to the Aperture device before proceeding. Prevents the Claude Code step from starting before the tailnet connection is ready. |
| `anthropic_api_key: "-"` | Placeholder that satisfies the action's non-empty check. Aperture manages the real API key. |
| `ANTHROPIC_BASE_URL: "http://<aperture-hostname>"` | Redirects all API calls to the Aperture device. MagicDNS resolves `<aperture-hostname>` to the Aperture device's Tailscale IP address. |
| `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"` | Prevents Claude Code from sending telemetry or update checks that would bypass Aperture. |

`anthropic_api_key` is a `with:` input (action parameter), while `ANTHROPIC_BASE_URL` and `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` are `env:` variables. The action performs a truthy check on `anthropic_api_key` and refuses to start without it.

### [Permissions](/content/docs/aperture/how-to/use-claude-code-action#permissions/index.html)

The `permissions` block sets the minimum GitHub token permissions the workflow needs:

| Permission | Purpose |
| --- | --- |
| `id-token: write` | Required for workload identity federation. Omit if you use an OAuth client. |
| `pull-requests: write` | Lets the Claude Code Action post review comments on the pull request. |
| `contents: read` | Lets the Claude Code Action read repository files. |

## [Verify the connection](/content/docs/aperture/how-to/use-claude-code-action#verify-the-connection/index.html)

1. Open a pull request with a small code change to trigger the workflow.
2. In the workflow run's **Actions** tab, confirm:
   - The **Connect to tailnet** step connected and the `ping: <aperture-hostname>` check succeeded.
   - The **Run Claude Code** step completed and shows API calls to `http://<aperture-hostname>`.
3. Open the Aperture dashboard at `http://<aperture-hostname>/ui/` from a device in your tailnet. Go to the **Logs** page (admin only) and confirm the request appears with `tag:ci` as the caller identity.

### [Troubleshoot common issues](/content/docs/aperture/how-to/use-claude-code-action#troubleshoot-common-issues/index.html)

| Symptom | Cause | Fix |
| --- | --- | --- |
| Connection timeout in the Tailscale step | `tag:ci` does not exist in `tagOwners`, or the runner cannot reach the coordination server. | Verify the tag exists in your tailnet policy file and your GitHub secrets are correct. |
| HTTP 403 from Aperture | The grant is missing a `role` entry, or the `models` pattern does not match the requested model. | Verify the grant includes both `{ "role": "user" }` and a matching `{ "models": "..." }` entry. |
| Claude Code reports an API key error | `anthropic_api_key` is empty or missing. | Set `anthropic_api_key: "-"` in the `with:` block. The value must be non-empty. |
| `ping: <aperture-hostname>` times out | The network grant does not allow `tag:ci` to reach the Aperture device on port 80. | Check the `dst` and `ip` fields in your network access grant. |

## [Next steps](/content/docs/aperture/how-to/use-claude-code-action#next-steps/index.html)

- [Set per-user spending limits](/content/docs/aperture/how-to/set-per-user-spending-limits/index.html) to cap how much CI pipelines spend on AI requests.
- Restrict CI runners to specific models by narrowing the `models` pattern in the grant, for example `anthropic/claude-sonnet-4-6` instead of `anthropic/**`.
- [Export usage data to S3](/content/docs/aperture/how-to/export-usage-data-to-s3/index.html) for long-term retention and record-keeping.
- [Build a custom webhook](/content/docs/aperture/how-to/build-custom-webhook/index.html) to receive real-time notifications when CI runners make AI requests.
- For a complete guide that sets up AI-powered code reviews with cost control and usage visibility, refer to [route AI code reviews through Aperture](/content/docs/solutions/route-ai-code-reviews-through-aperture/index.html).
