Headless CI
Headless CI: your coding agent in GitHub Actions
Headless CI runs your coding agent in a CI job through the Context Mode gateway. You add the gateway's base URL and a few request headers to the workflow. The agent, its own install, the prompt and the model stay the same.
Key facts
- What it is
- Two env lines for a CI job:
ANTHROPIC_BASE_URLandANTHROPIC_CUSTOM_HEADERS.npx @context-mode/cli ci initwrites them into a GitHub Actions workflow for you. - Who it is for
- Teams that run a coding agent on pull requests, CI failures, issues or releases.
- Clients
- Claude Code headless (
claude -p),anthropics/claude-code-actionand the Claude Agent SDK. Codex, OpenCode and Pi connect by base URL too: their lines. - Measured result
- 24% to 34% lower cost per run on 4 CI jobs, Claude Code headless with Sonnet 5.5, 2026-11-01: the table and Benchmarks.
- Limits
- Needs a CI key, which a signed-in terminal makes. GitHub gives no secrets to pull requests from forks, so the workflow skips them. The table's figures are for Claude Code with Sonnet 5.5.
Turn it on
Run these in your repo. First sign your terminal in, once:
npx @context-mode/cli login
Make a CI key and save it as the repo secret CONTEXT_MODE_KEY. --gh saves it with the GitHub CLI. The repo comes from your git origin, or from --repo owner/name:
npx @context-mode/cli ci key create --gh
Write the workflow:
npx @context-mode/cli ci init
Add your model key as the secret ANTHROPIC_API_KEY. Then open a pull request: the review runs through Context Mode.
What ci init writes
.github/workflows/context-mode-review.yml, word for word. The only Context Mode lines are in the job's env block.
# Context Mode + anthropics/claude-code-action: Claude reviews every pull request.
# Written by `npx @context-mode/cli ci init`. The only Context Mode lines are in the job "env" block below.
# Secrets: ANTHROPIC_API_KEY (your model key, as the action needs today) and CONTEXT_MODE_KEY (a CI key from
# `npx @context-mode/cli ci key create`). This file holds no key.
name: context-mode-review
on:
pull_request:
types: [opened, synchronize, ready_for_review]
permissions:
contents: read
pull-requests: write
concurrency:
# One review per PR at a time; a new push cancels the old review.
group: context-mode-review-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
review:
# Fork PRs get no secrets (GitHub rule), so skip them instead of failing.
if: github.event.pull_request.head.repo.full_name == github.repository && !github.event.pull_request.draft
runs-on: ubuntu-latest
timeout-minutes: 20
env:
# --- Context Mode: start ---------------------------------------------
# 1. Send the agent's model requests through the gateway.
ANTHROPIC_BASE_URL: https://gateway.contextmode.ai
# 2. One header per line. The gateway strips every x-cm-* header before it
# forwards, so CONTEXT_MODE_KEY never reaches Anthropic.
# x-context-mode-banner: off keeps the savings line out of the review text.
# x-cm-ci-* tell the gateway which repository, pull request and run sent the request.
ANTHROPIC_CUSTOM_HEADERS: |
x-cm-device-key: ${{ secrets.CONTEXT_MODE_KEY }}
x-context-mode-banner: off
x-cm-ci-repo: ${{ github.repository }}
x-cm-ci-ref: pr-${{ github.event.pull_request.number }}
x-cm-ci-run: ${{ github.run_id }}
# 3. Claude Code turns tool search off for a non-first-party base URL; turn it back on.
ENABLE_TOOL_SEARCH: "true"
# --- Context Mode: end -----------------------------------------------
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
prompt: |
Review pull request #${{ github.event.pull_request.number }} in ${{ github.repository }}.
Compare the PR head with origin/${{ github.event.pull_request.base.ref }}.
Report only real problems: bugs, missing tests, security issues, breaking changes.
For each one give the file, the line and a one-line fix.
Post the result as one PR comment with `gh pr comment`. If you find nothing, say so in one line.
claude_args: |
--max-turns 20
--allowedTools "Bash(git diff:*),Bash(git log:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Read,Grep,Glob"
claude -p in your own job
For a CI-failure fix, triage or release notes, put the same env block on the job. Claude Code's own installer comes first:
env:
ANTHROPIC_BASE_URL: https://gateway.contextmode.ai
ANTHROPIC_CUSTOM_HEADERS: |
x-cm-device-key: ${{ secrets.CONTEXT_MODE_KEY }}
x-context-mode-banner: off
x-cm-ci-repo: ${{ github.repository }}
x-cm-ci-ref: pr-${{ github.event.pull_request.number }}
x-cm-ci-run: ${{ github.run_id }}
ENABLE_TOOL_SEARCH: "true"
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
steps:
- run: |
curl -fsSL https://claude.ai/install.sh | bash
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- run: claude -p "The tests fail. Find the cause and fix it." --allowedTools "Bash,Read,Edit"
Claude Code turns tool search off for a base URL that is not Anthropic's, so the block sets ENABLE_TOOL_SEARCH: "true". The Claude Agent SDK reads the same env vars, with no code change.
Measured result
24% to 34% lower cost per run on 4 CI jobs. Each task ran twice, back to back: direct to Anthropic, and through the gateway with the lines ci init writes. Same agent, same prompt, same model.
| CI job | Saved | Direct | Context Mode | Per 1,000 runs | Cheaper | Quality | Evidence |
|---|---|---|---|---|---|---|---|
| CI-failure fix | 33.9%Sonnet 5.5, 95% range 25.1 to 43.6% | $0.0442 | $0.0281 | $16 | 6 of 6 | Tests pass 7 of 7 in both | Workflow · Tasks |
| Issue triage and labels | 29.6%Sonnet 5.5, 95% range 21.1 to 38.8% | $0.0262 | $0.0183 | $8 | 6 of 6 | Label F1 pass 5 of 7 direct, 4 of 7 with Context Mode | Workflow · Tasks |
| Release notes | 25.7%Sonnet 5.5, 95% range 7.2 to 42.7% | $0.1333 | $0.0987 | $35 | 4 of 5 | Written notes, no pass mark | Workflow · Tasks |
| @claude fix (issue to PR) | 23.7%Sonnet 5.5, 95% range 11.4 to 35.7% | $0.0902 | $0.0736 | $17 | 6 of 6 | Tests pass 5 of 7 in both | Workflow · Tasks |
Claude Code headless (claude -p), Sonnet 5.5, 2026-11-01, the live gateway. Cost per run at list price, warm runs. Saved: each task gives a cost ratio, Context Mode over direct; the saving is 1 minus the geometric mean of those ratios, so it can differ from the two mean costs. Per 1,000 runs: the direct cost minus the Context Mode cost, times 1,000. Proof: the round · raw results (in the lab) · data file · the plan, written 2026-10-31 19:27 UTC, before the first run at 2026-11-01 04:22 UTC · reproduce it.
Run the same jobs in your own repo: make a copy of the lab. Your key stays with you.
Codex, OpenCode and Pi
Each agent installs its own way first. Then its gateway config adds the Context Mode lines.
Codex (codex exec)
Job env: CONTEXT_MODE_KEY: ${{ secrets.CONTEXT_MODE_KEY }} and the agent's own model key, OPENAI_API_KEY. Then the agent's own install, its gateway config and the run:
npm i -g @openai/codex
cat >> ~/.codex/config.toml <<'TOML'
model_provider = "context-mode"
[model_providers.context-mode]
name = "context-mode"
base_url = "https://gateway.contextmode.ai"
wire_api = "responses"
env_key = "OPENAI_API_KEY"
env_http_headers = { "x-cm-device-key" = "CONTEXT_MODE_KEY", "x-cm-ci-repo" = "GITHUB_REPOSITORY", "x-cm-ci-run" = "GITHUB_RUN_ID" }
TOML
codex exec "Review this pull request ..."
OpenCode (opencode run)
Job env: CONTEXT_MODE_KEY: ${{ secrets.CONTEXT_MODE_KEY }} and the agent's own model key, ANTHROPIC_API_KEY. Then the agent's own install, its gateway config and the run:
curl -fsSL https://opencode.ai/install | bash
opencode.json in the repo root:
{ "provider": { "anthropic": { "options": { "baseURL": "https://gateway.contextmode.ai/v1",
"headers": { "x-cm-device-key": "{env:CONTEXT_MODE_KEY}", "x-context-mode-banner": "off" } } } } }
opencode run "Review this pull request ..."
Pi (pi -p)
Job env: CONTEXT_MODE_KEY: ${{ secrets.CONTEXT_MODE_KEY }} and the agent's own model key, ANTHROPIC_API_KEY. Then the agent's own install, its gateway config and the run:
npm install -g @earendil-works/pi-coding-agent
~/.pi/agent/models.json:
{ "providers": { "anthropic": { "baseUrl": "https://gateway.contextmode.ai",
"headers": { "x-cm-device-key": "${CONTEXT_MODE_KEY}", "x-cm-client": "pi" } } } }
pi -p "Review this pull request ..."
What each header does
| Header | What it does |
|---|---|
x-cm-device-key | Names your account, for usage and billing. |
x-cm-ci-repo | Marks the request as CI: no personal memory, cost per repo. |
x-cm-ci-ref | The pull request or the branch. |
x-cm-ci-run | Cost per run, and the gap between runs that sets how long the cache lives. |
x-context-mode-banner: off | Keeps the savings line out of the review text. |
The gateway removes every x-cm-* header before it forwards. None of them reaches the model provider.