Claude Code headless mode: claude -p for scripts, CI and cron jobs

Claude Code headless mode: claude -p for scripts, CI and cron jobs

Claude Code headless mode is running Claude Code without its interactive interface: you pass a prompt with claude -p, it works through the task, prints the result and exits. This guide covers the flags that matter, output formats, how permissions behave when nobody can answer a prompt, --bare, resuming sessions, and safe setups for CI and cron.

In shortTL;DR
  • claude -p "task" runs one non-interactive turn and exits with code 0 on success, so you can call it from any script, CI job or cron entry.
  • Use --output-format json to get the result, session ID and an estimated cost as JSON, or add --json-schema to force a specific structure.
  • In a -p run with no permission mode set, actions that would need approval are denied, so pre-approve exactly what the task needs with --allowedTools and --permission-mode dontAsk.
  • Add --bare in CI so the run ignores local hooks, plugins, MCP servers and CLAUDE.md and behaves the same on every machine; it needs ANTHROPIC_API_KEY.
  • Cap every unattended run with --max-turns and --max-budget-usd, and let your script, not Claude, push branches and open pull requests.

What is Claude Code headless mode?

Claude Code headless mode, also called non-interactive or print mode, runs Claude Code from a single command with no chat interface. You add -p (or --print) and a prompt, Claude Code reads files, edits code and runs commands as usual, then prints the final answer and exits.

terminal
claude -p "What does the auth module do?"

Anthropic's headless docs describe claude -p as the command-line form of the Agent SDK: the same tools, agent loop and context management as interactive Claude Code, packaged for scripts and CI. If you need callbacks and typed messages in your own program, the Python and TypeScript SDK packages give you the same engine as a library.

What makes it useful in automation:

  • It behaves like a Unix tool. It reads stdin, writes stdout and exits with 0 on success or a non-zero code on failure.
  • It speaks JSON. You can parse the result, the session ID and an estimated cost.
  • It respects your configuration. Permission rules, hooks, skills and CLAUDE.md apply, unless you turn discovery off with --bare.

Typical jobs: a nightly dependency bump, a scheduled flaky-test hunt, triaging new issues, writing a changelog, reviewing a diff in CI, or a lint step that catches what regex can't.

Which claude -p flags do you need?

Most headless runs combine -p with four kinds of flags: what Claude may do, how output comes back, how long it may run, and which context it loads. All of these are in the CLI reference.

FlagWhat it does
-p, --printRun non-interactively and print the result
--output-formattext (default), json or stream-json
--json-schemaReturn validated JSON matching your schema
--allowedToolsTools that run without asking, e.g. "Read,Edit,Bash(npm test *)"
--disallowedToolsDeny rules; a bare name removes the tool entirely
--permission-modeBaseline: default, acceptEdits, plan, auto, dontAsk, bypassPermissions
--permission-prompts noneDeny anything that would prompt and tell Claude not to retry it
--max-turnsStop after N agentic turns (exits with an error)
--max-budget-usdStop once estimated spend reaches this amount
--modelsonnet, opus, haiku, fable or a full model name
--append-system-promptAdd instructions on top of the default system prompt
--bareSkip discovery of hooks, skills, plugins, MCP servers, memory and CLAUDE.md
--continue, --resume <id>Continue the latest or a specific conversation
--no-session-persistenceDon't save the session to disk

Piping works the way you would expect. This writes an explanation of a build log to a file:

terminal
cat build-error.txt | claude -p "Explain the root cause of this build error in 3 sentences" > explanation.txt

Piped stdin is capped at 10 MB. For bigger inputs, write a file and name its path in the prompt.

You can also call your own skills: put /skill-name in the prompt string and Claude Code expands it before the run. Our guide to Claude Code skills shows how to write one. Only the first skill in a -p prompt loads; the rest of the line becomes its arguments.

Which output formats does claude -p support?

claude -p returns plain text by default, a single JSON object with --output-format json, or a stream of JSON events, one per line, with --output-format stream-json. Use JSON in anything a program reads.

FormatYou getUse it for
textThe final answerHumans, log files, quick shell use
jsonresult, session_id, usage and total_cost_usdScripts that parse the result or track spend
stream-jsonNewline-delimited events, ending with a result messageLive progress, long runs, dashboards

Extract fields with jq:

terminal
claude -p "Summarize this project in 5 bullets" --output-format json | jq -r '.result'

When a program needs a fixed shape, add --json-schema. The structured data arrives in the structured_output field:

terminal
claude -p "List every TODO comment in src/ with file and line" \  --output-format json \  --json-schema '{"type":"object","properties":{"todos":{"type":"array","items":{"type":"object","properties":{"file":{"type":"string"},"line":{"type":"integer"},"text":{"type":"string"}},"required":["file","line","text"]}}},"required":["todos"]}' \  | jq '.structured_output.todos'

If the run ends without valid structured output, for example because Claude declined, the field is missing, so check for it before you use it.

For token-by-token streaming, stream-json needs --verbose, and --include-partial-messages adds partial text deltas. The total_cost_usd figure is a client-side estimate that can differ from your bill.

How do permissions work when nobody can answer a prompt?

In a claude -p run with no permission mode set, Claude Code starts in Manual mode (default), and every action that would normally show a permission prompt is denied, because there is no one to ask. You decide up front what the run may do, with --allowedTools, a permission mode, or rules in your settings.

Calls that never need approval still run: file reads in your working directories and Claude Code's set of read-only commands. Everything else needs an explicit allowance. The permission modes docs suggest these setups:

GoalCommand shape
Read-only analysisclaude -p "..." with no extra flags, or --permission-mode plan
Edit files, run only listed commands--permission-mode acceptEdits --allowedTools "Bash(npm test *)"
Locked-down CI with an exact allowlist--permission-mode dontAsk --allowedTools "Read" "Bash(npm test)"
Hands-off with a safety classifier--permission-mode auto --permission-prompts none
No checks at all--dangerously-skip-permissions, inside a container or VM only

A few rules save debugging time:

  • Allow rules use permission syntax. Bash(git diff *) allows any command starting with git diff; the space before * matters, since Bash(git diff*) would also match git diff-index.
  • Deny rules win everywhere. A --disallowedTools entry or a deny rule in settings blocks the tool in every mode, including bypassPermissions.
  • Bypass refuses root. On Linux and macOS, Claude Code won't start in bypassPermissions as root or under sudo, outside a recognized sandbox.
  • Denials are visible. With stream-json, each denial appears as a permission_denied message and the final result lists them in permission_denials.
Tip

Start every new headless job in dontAsk with a short allowlist, run it once, and read the denials. Add only what the task needed. That gives you a minimal, reviewable permission set.

When should you use --bare?

Use --bare for CI and shared scripts. It skips auto-discovery of hooks, skills, custom commands, subagents, plugins, MCP servers, auto memory and CLAUDE.md, so the run starts faster and does the same thing on every machine. Anthropic recommends it for scripted calls and says it will become the default for -p in a future release.

Without --bare, claude -p loads everything an interactive session would, including a teammate's ~/.claude hooks or a project's .mcp.json. In bare mode Claude has the Bash, file read and file edit tools, and you pass context explicitly:

To loadFlag
Extra instructions--append-system-prompt, --append-system-prompt-file
Settings and permission rules--settings <file-or-json>
MCP servers--mcp-config <file-or-json>
Custom subagents--agents <file-or-json>
A plugin--plugin-dir <path>

Authentication is the catch. Bare mode never reads OAuth credentials or the keychain, so it needs ANTHROPIC_API_KEY from the Claude Console (or an apiKeyHelper in --settings); Amazon Bedrock, Google Cloud and Microsoft Foundry credentials work as usual. Without --bare, you can use a Claude subscription in CI: run claude setup-token once, and set the printed one-year token as CLAUDE_CODE_OAUTH_TOKEN, per the authentication docs.

How do you continue or resume a headless session?

Pass --continue to pick up the most recent conversation in the current directory, or --resume with a session ID to continue a specific one. Each call is still a separate claude -p run, but Claude keeps the earlier context.

terminal
# Capture the session ID from the first runsession_id=$(claude -p "Review src/billing for performance issues" --output-format json | jq -r '.session_id')# Later, from any directory on the same machineclaude -p "Now write a fix for the worst issue you found" --resume "$session_id"

Two details from the CLI reference trip people up:

  • claude --continue (interactive) skips sessions created with -p. claude -p --continue includes them. To open a headless session interactively, use claude --resume <id>.
  • Costs accumulate. With --continue or --resume, total_cost_usd and the --max-budget-usd check include the earlier runs in that conversation.

If you never need to resume, add --no-session-persistence so throwaway runs don't pile up on disk.

How do you run Claude Code headless in CI and cron?

Install Claude Code on the runner, supply credentials as a secret, and call claude --bare -p with a tight allowlist, a turn cap and JSON output. Keep git pushes and pull requests in your script so the agent never needs network-wide or write access to your remote.

A green analog alarm clock on a table
Scheduled runs need limits set before they start, because nobody is watching.

A nightly job with cron

Put the logic in a script and let cron call the script; that avoids crontab's special handling of % and keeps the job testable by hand.

scripts/nightly-test-fix.sh
#!/usr/bin/env bashset -euo pipefailcd /srv/repos/web-appexport ANTHROPIC_API_KEY="$(cat /etc/claude/api-key)"git fetch origingit switch -C claude/nightly-fix origin/mainclaude --bare -p "Run npm test. If tests fail, find the root cause and fix the code. Never edit test files. End with a summary of what you changed and why." \  --permission-mode dontAsk \  --allowedTools "Read" "Edit" "Bash(npm test *)" "Bash(npm run lint *)" \  --max-turns 40 \  --max-budget-usd 5 \  --output-format json > "/var/log/claude/nightly-$(date +%F).json"# The script, not Claude, decides what leaves the machineif ! git diff --quiet; then  git commit -am "fix: nightly test repair by Claude Code"  git push -f origin claude/nightly-fix  gh pr create --fill --head claude/nightly-fix || truefi
crontab -e
0 3 * * * /srv/repos/web-app/scripts/nightly-test-fix.sh >> /var/log/claude/cron.log 2>&1

Two limits to keep in mind. Without --bare, a -p run can wait for background work Claude started after it finishes; by default it stops idle background work after 10 minutes. And --max-budget-usd checks an estimate, so spend can pass the cap; leave headroom.

A CI step

Any CI system works the same way. Install with the native installer or npm (npm install -g @anthropic-ai/claude-code, which needs Node.js 22 or later), then run a read-only check:

ci step
git diff origin/main...HEAD | claude --bare -p \  "You review diffs for security problems. List each issue as file:line and one sentence. Print NONE if there are none." \  --permission-mode dontAsk --output-format json | jq -r '.result'

On GitHub, Anthropic's anthropics/claude-code-action@v1 wraps this for pull request comments and issue triggers; our Claude Code GitHub Actions guide covers it. If you also run OpenAI's agent, codex exec is the equivalent command, covered in our Codex CLI guide.

10 MBmaximum piped stdin for claude -p
10 mindefault wait for idle background work before a -p run stops it
143exit code when a -p run is stopped with SIGTERM
1 yearvalidity of a claude setup-token OAuth token
Sources: Claude Code docs, headless mode and authentication (November 2026)

How do you keep unattended Claude Code runs safe?

Treat every headless run as code running with your credentials and no one watching: limit what it can touch, cap how long it runs, and review its output before it ships. The defaults help, but a few behaviors in -p mode surprise people.

  • Repository config runs without a trust dialog. A -p run never shows the workspace trust prompt. In a folder you never trusted, the project's hooks, env block and .mcp.json servers still run and connect, according to the permissions docs. The project's permissions.allow rules are not used, so pass --allowedTools instead. Use --bare or --setting-sources user when you run against code you didn't write.
  • Skills can pre-approve tools. A project skill's allowed-tools applies in -p runs too. Review .claude/skills/ in any repository you automate.
  • Prompt injection is real. Issues, web pages and dependency files can carry instructions. Keep network access and secrets away from runs that read untrusted text; our guide to prompt injection in coding agents covers the patterns.
  • Make rules deterministic. A PreToolUse hook that blocks .env edits holds in every permission mode, while a line in CLAUDE.md is only advice.
  • Stop cleanly. SIGINT ends the current turn properly. SIGTERM exits with code 143, kills running commands, runs SessionEnd hooks and leaves the turn unfinished.
  • Isolate the machine. --dangerously-skip-permissions belongs in a container or VM with no credentials you would mind losing.
Warning

Never give a headless run your personal SSH keys or a token that can push to main. Give it a branch, let your script push, and merge through a reviewed pull request.

Headless runs on a laptop stop when the laptop sleeps, which is why most teams move scheduled jobs to a server. Orkio rents an always-on, isolated cloud box where Claude Code keeps working after you close your laptop, runs on your own Claude account, and opens pull requests you review from your phone. For other ways to keep agents running overnight, see how to run Claude Code 24/7.

Claude Code headless mode checklist

Before you schedule your first unattended run, check these:

  1. Run the exact command by hand once, in the same directory and with the same user as the job.
  2. Use --bare in CI and shared scripts, with ANTHROPIC_API_KEY stored as a secret.
  3. Start in --permission-mode dontAsk with the shortest --allowedTools list that works.
  4. Set --max-turns and --max-budget-usd on every scheduled run.
  5. Use --output-format json, log the whole object, and alert on non-zero exit codes.
  6. Work on a branch; let the script commit, push and open the pull request.
  7. Add hooks for rules that must never break, such as protected files.
  8. Review the first week of runs by hand before you trust the job.

Frequently asked questions

What is the difference between claude -p and the Agent SDK?

They are the same engine. Anthropic's docs describe claude -p as the Agent SDK's command-line form for scripts and CI. The Python and TypeScript SDK packages add native message objects, tool approval callbacks and structured outputs for programs that need finer control.

Can I use my Claude subscription for headless mode instead of an API key?

Yes, for normal runs. Run claude setup-token once to get a one-year OAuth token and set it as CLAUDE_CODE_OAUTH_TOKEN where the script runs. Bare mode is the exception: it never reads OAuth credentials, so a --bare run needs ANTHROPIC_API_KEY or an apiKeyHelper.

Why does claude -p say a tool was denied?

When no permission mode is set, a claude -p run starts in Manual mode, and anything that would normally show a permission prompt is denied because nobody can answer it. Add the exact tools to --allowedTools, set a mode such as acceptEdits or dontAsk, or allow them in your settings file.

Does Claude Code headless mode load CLAUDE.md and skills?

Yes, unless you pass --bare. A normal claude -p run discovers CLAUDE.md, skills, hooks, plugins and MCP servers like an interactive session. With --bare it skips all of them, and you pass what you need explicitly with flags such as --append-system-prompt, --settings and --mcp-config.

How do I stop a long claude -p run cleanly?

Send SIGINT to end the current turn properly. SIGTERM makes Claude Code exit with code 143, kill any running shell command, run SessionEnd hooks and leave the turn unfinished. Set --max-turns and --max-budget-usd up front so runs stop on their own.

SOURCES

  1. Claude Code docs: Run Claude Code programmatically
  2. Claude Code docs: CLI reference
  3. Claude Code docs: Choose a permission mode
  4. Claude Code docs: Configure permissions
  5. Claude Code docs: Authentication
  6. Claude Code docs: Advanced setup
  • #claude code
  • #headless
  • #automation
  • #ci/cd
Orkio team

The team building Orkio. We write about putting AI to work: coding agents that run overnight, always-on boxes and pull requests ready by morning.

Keep reading

All posts

07:06 · YOU MERGED #128

Go to sleep. Wake up to pull requests.

Start for $19 a month
© 2026 Orkio · Hosted in Europe TürkçeStatus