Claude Code Headless Mode: Flags, Auth and Cost (2026)
Claude Code headless mode means running Claude Code with no one at the keyboard. You call claude -p "your task", it does the work, prints a result and exits, the same way any shell command does. The official docs put it this way: "Add the -p (or --print) flag to any claude command to run it non-interactively" (Claude Code docs: Run Claude Code programmatically). The -p flag alone isn't enough for a reliable cron or CI job, though. You also need credentials that never ask for a browser login, permission settings that never wait for a human, a hard stop on turns and spend, and output your script can parse. This guide covers each of those.
Which option is right for you?
Use local claude -p with an API key for cron jobs on your own machine or server. Use the GitHub Action for anything triggered by repository events. Use a subscription token only for light personal automation.
| Your situation | What to pick first | First page to open |
|---|---|---|
| Beginner, trying one scheduled task on a laptop | claude -p with your existing Pro/Max login, no --bare | Run Claude Code programmatically |
| Daily work: nightly cron on a VPS or home server | claude --bare -p + ANTHROPIC_API_KEY + --max-budget-usd | CLI reference |
| PR reviews, issue triage, scheduled repo reports | anthropics/claude-code-action@v1 in GitHub Actions | Claude Code GitHub Actions |
| Tight budget, already paying for Pro or Max | claude setup-token → CLAUDE_CODE_OAUTH_TOKEN (not with --bare) | Authentication |
| Shared repos or a team secret | Console API key stored as an org-level secret | Authentication |
The reasoning in one line each:
- Beginner: you already have a login on the machine, so you can test the idea before you set up any billing.
- Daily cron: an API key behaves the same on every run, and bare mode keeps stray local config out of the job.
- GitHub events: the Action handles checkout, secrets and posting comments for you.
- Budget: the token draws from your subscription instead of per-token billing.
- Team: the GitHub Actions docs recommend an API key for shared secrets, because an OAuth token is tied to one person's subscription (GitHub Actions docs).
What is Claude Code headless mode?
Claude Code headless mode is the non-interactive way to run Claude Code. There's no terminal UI and no prompts. Each run takes one task, prints the result and exits. It is the same agent with the same tools as the interactive app, but it runs inside a different execution context, and that context has its own rules.
The docs describe it as the Agent SDK "available as a CLI for scripts and CI/CD" (headless docs). Several behaviors change when you add -p:
- Permissions. With Claude Code v2.1.302 or later,
claude -pruns start in Manual mode when nothing sets a permission mode. Any call that needs approval is denied unless an allow rule, a hook or a permission tool approves it (headless docs). Your job won't hang waiting for a "yes", but it may quietly fail to do the work. - Credentials. In
-pmode anANTHROPIC_API_KEYin the environment "is always used when present". Interactive sessions ask you first (Authentication docs). - Exit codes. Claude Code "exits with code 0 on success and a non-zero code when the run fails". Some failures, such as missing authentication, are printed as the result on stdout (headless docs).
- Slash commands. Skills and custom commands still work. Terminal-only commands such as
/logindo not. - Workspace trust. Without
--bare, a-psession runs the hooks in a project's.claude/settings.jsonand connects its.mcp.jsonservers "even in a folder you've never trusted" (headless docs).
That last point is the one people most often miss. If your cron job runs claude -p inside a repository you cloned from someone else, that repository's hooks will run. Bare mode prevents this.
Bare mode is the "real" headless mode
--bare skips auto-discovery of hooks, skills, plugins, MCP servers, auto memory and CLAUDE.md, so every machine produces the same result. The docs say plainly that "--bare is the recommended mode for scripted and SDK calls, and will become the default for -p in a future release" (headless docs). If you need any of that context, pass it in explicitly: --append-system-prompt-file for instructions, --settings for settings, --mcp-config for MCP servers, --agents for subagents.
The limitation: bare mode never reads OAuth credentials or the keychain. That rules out your subscription login (more on this in the auth section).
How do you run Claude Code in headless mode?
Run claude -p with a non-interactive credential, explicit tool permissions, turn and budget caps, and JSON output. Here is the sequence for a cron job on Linux or macOS.
- Install and check the version. Run
claude --version. Several flags below need recent builds:--permission-promptsneeds v2.1.259 or later, and the Manual-mode default for-pneeds v2.1.302 (CLI reference). Update withclaude update. - Create an API key. In the Claude Console, create a key. Save it in a file only your user can read, for example
~/.config/claude-cron.envwithchmod 600. - Confirm auth works. Run
claude auth status. It prints JSON, andauthMethodshould readapi_key(oroauth_tokenif you use a subscription token). It "Exits with code 0 if logged in, 1 if not" (CLI reference). - Do a dry run by hand. From the project directory:
``bash claude --bare -p "List the TODO comments in src/ and group them by file" \ --allowedTools "Read,Grep,Glob" \ --max-turns 10 \ --output-format json | jq -r '.result' ``
- Wrap it in a script (example):
``bash #!/usr/bin/env bash set -euo pipefail source ~/.config/claude-cron.env # exports ANTHROPIC_API_KEY cd /home/you/projects/my-app mkdir -p logs /home/you/.local/bin/claude --bare -p "Run the test suite and fix failing tests in src/ only" \ --allowedTools "Read,Edit,Bash(npm test *)" \ --permission-mode dontAsk \ --max-turns 20 \ --max-budget-usd 3.00 \ --output-format json > "logs/run-$(date +%F).json" ``
- Schedule it. Run
crontab -eand add a line such as0 3 * /home/you/bin/nightly-claude.sh >> /home/you/logs/cron.log 2>&1. For scheduling details and overnight patterns, see Claude Code Cron Job Setup: Run Automations Overnight.
Common mistakes and fixes
claude: command not foundunder cron. Cron uses a minimalPATH. Fix: call the absolute path fromwhich claude.- The job "succeeds" but nothing changed. In Manual mode, unlisted tools that need approval are denied. Fix: add the exact tools to
--allowedTools, or readpermission_denialsin the result (see monitoring below). - "Not logged in" only under cron. Your interactive login lives in the macOS Keychain or
~/.claude/.credentials.json(Authentication docs), and--bareignores both. Fix: exportANTHROPIC_API_KEYin the script. - The run hangs for minutes after finishing. A
-prun can wait for background work. By default it stops idle background tasks after 10 minutes, and you can change that withCLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS(headless docs). Also see Why Claude Code Stops Mid-Automation (and How to Fix It). - Piping a huge log. Piped stdin is capped at 10MB. Above that, Claude Code exits non-zero, so write the content to a file and reference the path instead (headless docs).
Which Claude Code CLI flags matter in headless mode?
Besides -p, six flags matter most: --bare, --output-format, --allowedTools, --permission-mode, --max-turns and --max-budget-usd. They decide whether a headless run is safe and parseable, and everything else is optional tuning. All of the following come from the CLI reference as of October 2026.
| Flag | What it does in a headless run | Default / note |
|---|---|---|
-p, --print | Non-interactive run, then exit | Required |
--bare | Skips hooks, skills, plugins, MCP, CLAUDE.md discovery | Off today; planned default for -p |
--output-format | text, json, or stream-json | text |
--json-schema | Validated structured output in structured_output | Print mode only |
--allowedTools | Tools that run without approval, e.g. "Bash(git diff *)" | Uses permission rule syntax |
--disallowedTools | Deny rules; a bare name removes the tool entirely | "mcp__*" removes all MCP tools |
--permission-mode | default/manual, acceptEdits, plan, auto, dontAsk, bypassPermissions | Manual for -p (v2.1.302+) |
--permission-prompts none | Denies anything that would prompt; tells Claude not to retry | v2.1.259+ |
--max-turns | Caps agentic turns; "Exits with an error when the limit is reached" | "No limit by default" |
--max-budget-usd | Stops once estimated spend hits the cap | Print mode only; spend can pass the cap |
--model / --fallback-model | Pick a model; fall back when overloaded | e.g. --model sonnet |
--no-session-persistence | Don't save the session to disk | Print mode only |
--continue / --resume <id> | Chain several -p calls into one conversation | Use session_id from JSON |
A few of these have traps:
- **The space in
Bash(git diff *)matters.** The docs warn that without it,Bash(git diff*)"would also matchgit diff-index" (headless docs). --max-turnsexits non-zero when it hits the cap. Your wrapper should treat that as "incomplete", not "crashed".--max-budget-usdis a soft ceiling. It is checked against a client-side estimate, and "Spend can pass the cap, so leave headroom" (CLI reference).bypassPermissions(--dangerously-skip-permissions) needs isolation. The docs say to "Only use this mode in isolated environments like containers, VMs, or dev containers without internet access" (Permission modes). On a personal server that has your SSH keys, preferdontAskwith a narrow allow list. For a ready-made allow/deny set, see Claude Code Permissions Best Practices: Headless Template.
What is headless/CI mode in Claude Code?
Headless/CI mode is claude -p running inside a CI pipeline, usually via the official GitHub Action or a GitLab CI job. The pipeline supplies the credentials, the checkout and the trigger. Claude Code has no separate "CI mode" switch. In CI the runner is disposable and shared, so reproducibility and secret handling matter more than they do on your own machine.
The Claude Code GitHub Action (anthropics/claude-code-action@v1) has two modes:
- Interactive mode. No
promptinput. Claude waits for an@claudemention in an issue or PR. - Automation mode. You set a
promptinput and Claude runs on any GitHub event, including a cron schedule.
The fastest setup is to run /install-github-app inside Claude Code. It installs the GitHub App, saves your secret as ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN, and opens a pull request with the workflow. You need admin access to the repository, and the command works only for github.com repositories (GitHub Actions docs).
Example scheduled workflow, adapted from the official daily-report example:
name: Nightly triage
on:
schedule:
- cron: "0 9 * * *"
jobs:
triage:
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
issues: read
id-token: write
steps:
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: "Summarize yesterday's commits and open issues"
claude_args: |
--model sonnet
--max-turns 8
--allowedTools "mcp__github__list_commits,mcp__github__list_issues"
CI-specific pitfalls from the official docs:
- No tools by default. With a plain-text prompt, "Claude has no shell or GitHub API access until you grant the tools the prompt needs" (GitHub Actions docs).
- Schedules can stop on their own. GitHub runs scheduled workflows only from the default branch. In public repos it disables the schedule after 60 days without repository activity (GitHub Actions docs).
- Fork PRs get no secrets. GitHub withholds secrets from fork pull requests on public repos, so reviews run only on same-repo branches (GitHub Actions docs).
- Claude's commits don't trigger your CI. That happens if you pass the default
GITHUB_TOKEN. Remove it so the Action authenticates as the Claude GitHub App (GitHub Actions docs). - Old workflows. Workflows that still reference
@betashould move to@v1, withmax_turnsandmodelmoved intoclaude_args.
How do you authenticate Claude Code for cron and CI runs?
Use an environment variable, either ANTHROPIC_API_KEY (Console billing) or CLAUDE_CODE_OAUTH_TOKEN (subscription). Never rely on an interactive browser login, because a cron job has no browser to finish it.
When more than one credential is present, Claude Code checks them in this order (Authentication docs):
- Cloud provider variables (
CLAUDE_CODE_USE_BEDROCK,_VERTEX,_FOUNDRY) ANTHROPIC_AUTH_TOKEN, used for LLM gatewaysANTHROPIC_API_KEYapiKeyHelperscript output, for rotating keys from a vaultCLAUDE_CODE_OAUTH_TOKEN- Anthropic profile / federation credentials
- Subscription login from
/login
Option A: API key (recommended for unattended work)
- Create a key in the Claude Console.
- Export it in your job's environment, or store it as a CI secret.
- Works with
--bare.
When you first authenticate with Console, a workspace called "Claude Code" is created automatically. You can set spend limits on it (Costs docs).
Option B: subscription token
- On a machine with a browser, run
claude setup-tokenand approve access. - Copy the printed token. The command "does not save the token anywhere".
- Set
export CLAUDE_CODE_OAUTH_TOKEN=your-tokenin the job's environment.
The token lasts one year and is meant for "CI pipelines and scripts where browser login isn't available". It requires a Pro, Max, Team or Enterprise plan (Authentication docs).
Pitfalls with fixes:
- Bare mode ignores this token. "Bare mode does not read
CLAUDE_CODE_OAUTH_TOKEN" (Authentication docs). If you want--bare, you need option A. - A forgotten API key silently wins. If
ANTHROPIC_API_KEYis set anywhere in the job's environment, it beats your subscription token and you get API charges. Rununset ANTHROPIC_API_KEYin subscription-billed scripts. - The token can only make model requests. It can't fetch claude.ai connectors or start Remote Control. Locally configured MCP servers still work.
- A year passes faster than you think. Put a calendar reminder 11 months out to regenerate it.
How much does Claude Code headless mode cost?
Claude Code headless mode costs exactly what interactive use costs for the same tokens. You pay per token on an API key, or the usage comes out of your plan's limits on a subscription. There is no headless surcharge. What makes it expensive is repetition: a job that runs nightly, retries on failure, or has no turn cap can multiply spend without anyone noticing.
Official numbers (as of October 2026; check the official page):
| Item | Price | Source |
|---|---|---|
| Opus 5.5 | $4 input / $20 output per million tokens | Claude API pricing |
| Sonnet 5.5 | $2 input / $10 output per million tokens | Claude API pricing |
| Haiku 5.5 | $0.10–$0.50 input / $0.50–$2.50 output per million tokens, depending on prompt length | Claude API pricing |
| Pro plan | $20/month ($17/month billed annually) | Claude pricing |
| Max plans | from $100/month | Claude pricing |
| Enterprise average | ~$13 per developer per active day; $150–250 per month | Costs docs |
Model choice matters a lot here. Pro, Max, Team, Enterprise and the Anthropic API all default to Opus 5.5 (Model configuration), so a headless job without --model runs on the most expensive of the three. The Costs docs note that Sonnet "handles most coding tasks well and costs less than Opus".
Example estimate (illustrative, not a measurement). Suppose a nightly job reads 200,000 input tokens and writes 20,000 output tokens on Sonnet 5.5, ignoring caching. That's 0.2 × $2 + 0.02 × $10 = $0.60 per run, or about $18 for 30 nights. The same job on Opus 5.5 would be about $1.20 per run. Prompt caching usually lowers the input side, so treat this as an upper-bound sketch.
Cost controls that actually apply to headless runs:
--max-budget-usd 2.00on every scheduled call--max-turnssized to the task (5–10 for a report, 20+ for fix-and-test loops)--model sonnet, orhaikufor summarize-only jobs- Workspace spend limits in the Console for an API key
- In GitHub Actions, workflow
timeout-minutesand concurrency limits. Runners also bill GitHub Actions minutes on top of tokens (GitHub Actions docs)
For more ways to cut token use, see Claude Code: How to See Token Usage (and Reduce It).
Does Claude Code headless mode work with a Pro or Max subscription?
Yes. Claude Code headless mode works on Pro and Max through claude setup-token. Every automated run then draws from the same limits as your chat and interactive coding. Anthropic's help center says the limits "are shared across Claude and Claude Code", and both plans have "a five-hour session limit and a weekly limit" (Claude Help Center).
Subscription token (CLAUDE_CODE_OAUTH_TOKEN) | API key (ANTHROPIC_API_KEY) | |
|---|---|---|
| Billing | Included plan usage | Per token, pay as you go |
Works with --bare | No | Yes |
| Hits limits | 5-hour session and weekly windows, shared with your chats | Organization rate limits |
| Good for | A few light personal jobs | Nightly jobs, CI, anything shared |
| Shared team secret | Not recommended (tied to one person) | Recommended |
| Spend cap | Plan limits; optional usage-credit spend limit | --max-budget-usd plus Console workspace limits |
How to choose:
- Pick the subscription token if you run one or two small jobs a day and want a fixed monthly bill. The limitation: a heavy overnight job can use up your session window, and you hit "You've hit your session limit" the next morning when you sit down to work interactively.
- Pick the API key if the job matters to someone else, runs in CI, or runs often. It is not the right choice if you need a guaranteed fixed monthly cost, because per-token billing varies with the work.
When a scheduled job hits a limit, retry logic matters more than the plan. See Claude Code Rate Limit Reached: Retry Setup for Cron Jobs. In stream-json output, retries show up as system/api_retry events, with an error field such as rate_limit or billing_error (headless docs).
How do hooks work in Claude Code headless runs?
Claude Code hooks run in headless -p runs the same way they do in interactive sessions, unless you pass --bare, which skips hook discovery. That makes hooks the main way to add guardrails and logging to a job nobody watches.
The events most useful for unattended runs, per the hooks reference:
PreToolUse. Exit code 2 blocks the tool call. Use it to refuserm -rfor pushes tomain.PermissionRequest. Can allow or deny on the user's behalf throughhookSpecificOutput.decision.behavior. In a-prun this is one of the few things that can approve a call Manual mode would otherwise deny.Stop. Fires when Claude finishes responding. Good for "did the tests actually pass?" checks.SessionEnd. Fires when a session terminates and is meant for cleanup and logging. It also runs when you stop a-prun with SIGTERM, which exits with code 143 (headless docs).
Steps to attach a hook to a headless job:
- Write the hook command into a settings file, for example
ci-settings.json, under"hooks": { "PreToolUse": [...] }. - Without bare mode, put it in the project's
.claude/settings.json. With--bare, pass it explicitly:--settings ./ci-settings.json(the documented way to load settings in bare mode). - Add
--output-format stream-json --verbose --include-hook-eventsto a test run, and confirm thathook_started/hook_responseevents appear. - Keep hooks fast. The docs' cost example uses a
PreToolUsehook to rewrite test commands so only failures reach Claude, cutting context "from tens of thousands of tokens to hundreds" (Costs docs).
Ready-made scripts are in Claude Code Hooks Examples: 7 Copy-Paste Scripts, and event order is covered in Claude Code Hooks Lifecycle: Every Event, Explained.
How do you track Claude Code usage from cron jobs?
Track Claude Code usage from cron jobs by saving the --output-format json result of every run. Each one includes total_cost_usd, a per-model cost breakdown and the session_id. Unattended runs have no screen, so if you don't log this, nobody sees it.
Example log line extraction (illustrative):
jq -c '{date: now|todate, session: .session_id, cost: .total_cost_usd, ok: (.is_error|not)}' \
logs/run-$(date +%F).json >> logs/costs.jsonl
The session_id, result and total_cost_usd fields are documented. For any other field name (such as is_error above), check one real output file before you depend on it.
What to watch:
- Cost per run. The docs say "Both figures are client-side estimates and can differ from your actual bill" (headless docs). For billing truth, use the Console usage page.
- Resumed conversations. With
--continueor--resume, the reported total includes earlier runs' spend. Store per-run differences, or you'll double count. - Silent permission failures. In
stream-json, denials appear aspermission_deniedmessages, and the final result lists them inpermission_denials. A non-empty list on a "successful" run means the job didn't do what you think. - MCP servers that never loaded. The
system/initevent carriesmcp_server_errors, and the docs suggest a CI gate "can fail on a non-empty array" (headless docs). - Missing structured output. If you use
--json-schema, check thatstructured_outputexists before using it. It is absent when Claude declines.
To get alerts when something breaks rather than just a log, see AI Agent Monitoring in Claude Code: Catch Silent Failures.
Next steps
Do today:
- Run
claude --versionandclaude auth status. Confirm you're on v2.1.302 or later and see whichauthMethodyour scripts would actually use. - Take one task you do by hand every week and run it once as
claude --bare -p "..." --allowedTools "Read" --max-turns 10 --max-budget-usd 1 --output-format json. Read the JSON before you schedule anything. - Add
--model sonnetand a--max-budget-usdcap to every existing scheduled call that lacks them.
If you'd rather not build this yourself: Anthropic's Code Review gives you automatic PR reviews "without writing a workflow", and the Claude Code GitHub Action covers scheduled repo tasks with a one-command setup (/install-github-app). Both run on your API key or subscription. The Action also uses GitHub Actions minutes. They suit people whose recurring work lives in GitHub. They don't fit jobs that touch local files, private servers or non-GitHub data, where a local cron script is still simpler. If what holds you back is subscription limits rather than setup, a Max plan (from $100/month as of October 2026; check claude.com/pricing) raises the ceiling. It is only worth it if you also use Claude Code interactively most days. For automation alone, pay-as-you-go API billing is usually easier to cap.
Related guides on this site:
- Claude Code Cron Job Setup: Run Automations Overnight
- Claude Code Permissions Best Practices: Headless Template
- AI Agent Guardrails Example: Copy-Paste Claude Code Rules
- Claude Code Stuck on Compacting Conversation: Cron Watchdog
FAQ
Can Claude Code run headless?
Yes. Claude Code runs headless with claude -p "task", which runs the full agent without the terminal UI and exits when done. It works on macOS, Linux and Windows, in cron, systemd timers, Docker and CI runners. For repeatable results, add --bare and authenticate with ANTHROPIC_API_KEY.
What is the code for headless mode?
The minimal command is claude -p "your prompt". A safer unattended version is claude --bare -p "your prompt" --allowedTools "Read,Edit" --max-turns 15 --max-budget-usd 2 --output-format json. Pipe data in with cat file | claude -p "explain".
What is headless mode, in general?
Outside Claude Code, "headless" means software running without its graphical or interactive interface, like headless Chrome for browser automation or a headless server with no monitor. Claude Code uses the word the same way: the agent works, but there's no interface for a person to watch or answer.
What does Claude Code headless plan mode mean?
It means running claude -p "..." --permission-mode plan. In plan mode, "Claude reads files, runs shell commands to explore, and writes a plan, but does not edit your source" (Permission modes). Headless, that gives you a read-only report, such as a refactor proposal written to a log, with no risk of file changes. Shell commands outside the read-only set still need approval, so in -p they're denied unless allowed.
Does Claude Code headless mode use a different model than interactive mode?
No. Headless runs use the same default as interactive sessions, which is Opus 5.5 for Pro, Max, Team, Enterprise and Anthropic API accounts (Model configuration). Override it per run with --model sonnet or --model haiku, and add --fallback-model sonnet,haiku for when the primary model is overloaded.
Sources
- Claude Code docs: Run Claude Code programmatically (headless)
- Claude Code docs: CLI reference
- Claude Code docs: Authentication
- Claude Code docs: Permission modes
- Claude Code docs: Manage costs effectively
- Claude Code docs: GitHub Actions
- Claude Code docs: Hooks reference
- Claude Code docs: Model configuration
- Claude Code docs: Code Review
- Claude pricing (plans)
- Claude API pricing
- Claude Help Center: Using Claude Code with your Pro or Max plan
- Claude Console
Spotted something out of date or wrong? Tell us at shigoto.ai.note@gmail.com. Guides are re-checked against official sources on a rolling schedule.