# Claude Code headless mode: claude -p for scripts and CI

URL: https://agentvera.dev/en/blog/claude-code-headless-mode/ · Published: 2026-10-27

Claude Code headless mode explained: claude -p, JSON and stream-json output, --allowedTools, --max-turns, --bare, resuming sessions, piping input, and use in cron and CI.

**In short:**

- claude -p "prompt" runs Claude Code once without the interactive interface, prints the result and exits with 0 on success, so it fits shell scripts, cron jobs, git hooks and CI.
- Use --output-format json for one parseable object (result, session_id, cost estimate) or stream-json with --verbose for live events; --json-schema returns validated structured output.
- Pre-approve tools with --allowedTools, deny the rest with --permission-mode dontAsk, cap runs with --max-turns, and add --bare so the run ignores local hooks, MCP servers and CLAUDE.md.

Claude Code headless mode is the `-p` flag. `claude -p "your prompt"` runs Claude Code once without the interactive interface: it reads your prompt (and anything you pipe in), works with the tools you allow, prints the result and exits. Exit code 0 means success, anything else means the run failed. That makes it a normal command-line tool you can put in shell scripts, cron jobs, git hooks and CI pipelines.

This guide covers the flags you’ll actually use: output formats, tool permissions, turn and budget limits, `--bare`, continuing sessions and piping data, followed by ready-to-use patterns for scripts, cron and CI, and when to move to the Agent SDK. Every flag here is checked against the official CLI reference as of October 2026.

## The basics

```bash
claude -p "What does the auth module do?"
```

`-p` and `--print` are the same flag. Most interactive options combine with it, such as `--model`, `--effort`, `--append-system-prompt` and `--mcp-config`. A few options exist only in print mode, among them `--max-turns`, `--max-budget-usd`, `--json-schema` and `--no-session-persistence`.

Two details matter for scripts:

- If you pass an invalid flag, the error goes to stderr before the run starts.
- If something fails inside the run, such as missing authentication, the failure is printed as the result on stdout. Check the exit code, not just the output.

## Pipe data in, redirect results out

Headless mode reads stdin, so logs, diffs and files flow in like with any Unix tool:

```bash
cat build-error.txt | claude -p 'concisely explain the root cause of this build error' > output.txt
```

```bash
git diff main | claude -p "Review this diff for bugs. List each as file:line and one sentence."
```

Piped stdin is capped at 10MB. Above that, Claude Code exits with an error, so for big inputs write the content to a file and name the path in the prompt instead.

You can also wire it into `package.json`, as the official docs do with a typo linter:

```json
{
  "scripts": {
    "lint:claude": "git diff main | claude -p \"you are a typo linter. for each typo in this diff, report filename:line on one line and the issue on the next. return nothing else.\""
  }
}
```

## Choose an output format

`--output-format` takes three values:

| Format | What you get | Use it for |
| --- | --- | --- |
| `text` (default) | The final answer as plain text | Humans, files, simple scripts |
| `json` | One JSON object at the end | Scripts that need the answer plus metadata |
| `stream-json` | One JSON event per line as it happens | Live progress, custom UIs, logging |

The JSON object carries the answer in `result`, plus `session_id`, usage figures and `total_cost_usd`. That cost is a client-side estimate and can differ from your bill.

```bash
claude -p "Summarize this project" --output-format json | jq -r '.result'
```

### Structured output with --json-schema

When a script needs data rather than prose, pass a JSON Schema. The validated result lands in `structured_output`:

```bash
claude -p "Extract the main function names from auth.py" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}'
```

If the run ends without valid structured output, for example because Claude declined, the field is missing. Check for it before using it:

```bash
out=$(claude -p "Extract the main function names from auth.py" --output-format json --json-schema "$(cat schema.json)")
echo "$out" | jq -e '.structured_output' > /dev/null || { echo "no structured output" >&2; exit 1; }
```

### Streaming events

`stream-json` requires `--verbose`. Add `--include-partial-messages` to get tokens as they’re generated. This prints just the streaming text:

```bash
claude -p "Write a poem" --output-format stream-json --verbose --include-partial-messages | \
  jq -rj 'select(.type == "stream_event" and .event.delta.type? == "text_delta") | .event.delta.text'
```

The first event is normally `system/init`, with the model, tools, MCP servers and plugins. The last line is a `result` message with the final text, cost and session metadata. Retries show up as `system/api_retry` events. A useful CI gate: when you pass `--mcp-config`, a server entry that fails validation is skipped without failing the run, and it appears in `mcp_server_errors` on the `init` event. Fail the job if that array isn’t empty.

## Control what the run may do

There is nobody to answer a permission prompt in a script, so decide up front what Claude may do.

**`--allowedTools`** pre-approves tools using permission rule syntax:

```bash
claude -p "Look at my staged changes and create an appropriate commit" \
  --allowedTools "Bash(git diff *),Bash(git log *),Bash(git status *),Bash(git commit *)"
```

The space before `*` matters: `Bash(git diff *)` matches commands starting with `git diff`, while `Bash(git diff*)` would also match `git diff-index`.

**`--permission-mode`** sets the baseline for everything not listed. As of October 2026 (Claude Code v2.1.302 and later), a `-p` run starts in Manual mode when nothing sets a mode, and a call that needs approval is denied. Useful values:

| Mode | Behavior in a headless run |
| --- | --- |
| `dontAsk` | Anything not pre-approved is refused. The predictable choice for scripts |
| `acceptEdits` | File edits are allowed without asking |
| `plan` | Read and plan only, no edits |
| `auto` | A classifier reviews each action; a block denies that action and the run continues |
| `bypassPermissions` | No checks at all. Only inside a disposable container or VM |

**`--tools`** restricts which built-in tools exist at all, and **`--disallowedTools`** adds deny rules. **`--permission-prompts none`** tells Claude that nobody can approve requests so it doesn’t keep retrying them; denials are listed in `permission_denials` on the final result. Why bypassing permissions on your own machine is risky is covered in [Claude Code skip permissions](/en/blog/claude-code-skip-permissions/ "Claude Code skip permissions").

## Put limits on every unattended run

- **`--max-turns 20`** stops after that many agentic turns and exits with an error. No limit by default.
- **`--max-budget-usd 5.00`** stops when the estimated spend reaches the amount. It uses the client-side estimate and spend can pass the cap slightly, so leave headroom.
- **`timeout`** from your shell or CI step is still worth having. If you stop a run with SIGTERM, Claude Code exits with code 143, records no result for the unfinished turn and still runs `SessionEnd` hooks.

## Use --bare for reproducible runs

Without `--bare`, `claude -p` loads the same things as an interactive session: hooks, skills, plugins, MCP servers, auto memory and `CLAUDE.md`, from the working directory and from `~/.claude`. That means the same command behaves differently on your laptop and on a CI runner.

```bash
claude --bare -p "Summarize README.md" --allowedTools "Read"
```

`--bare` skips all of that discovery and starts faster. You pass in only what the run needs:

| To load | Flag |
| --- | --- |
| Extra instructions | `--append-system-prompt`, `--append-system-prompt-file` |
| Settings | `--settings <file-or-json>` |
| MCP servers | `--mcp-config <file-or-json>` |
| Custom agents | `--agents <file-or-json>` |
| A plugin | `--plugin-dir <path>` |

Bare mode never reads OAuth credentials or the system keychain. With the Anthropic API, set `ANTHROPIC_API_KEY` or provide an `apiKeyHelper` in the `--settings` JSON. The official docs recommend `--bare` for scripted calls and say it will become the default for `-p` in a future release, so writing it explicitly now keeps your scripts stable when that happens.

One more reason to care: without `--bare`, a `-p` run in a freshly cloned repo runs that repo’s project hooks and `.mcp.json` servers without a trust dialog. Don’t point a non-bare headless run at code you haven’t reviewed.

## Continue and resume sessions

Headless runs are saved like interactive ones, so you can chain steps:

```bash
# First request
claude -p "Review this codebase for performance issues"

# Continue the most recent conversation
claude -p "Now focus on the database queries" --continue
claude -p "Generate a summary of all issues found" --continue
```

When several runs happen at once, `--continue` may pick the wrong one. Capture the session ID instead:

```bash
session_id=$(claude -p "Start a review" --output-format json | jq -r '.session_id')
claude -p "Continue that review" --resume "$session_id"
```

`--resume` finds the session by ID from any directory on the machine, and also accepts a name or the path to a session’s `.jsonl` transcript. `--fork-session` starts a new branch from the resumed conversation instead of appending to it, and `--no-session-persistence` keeps one-off runs from being saved at all.

## Patterns for scripts, cron and CI

### A loop over files

The official best-practices page has a good template for a mechanical change across many files:

```bash
for file in $(cat files.txt); do
  claude --bare -p "Migrate $file to the new logger API and commit. Reply OK or FAIL." \
    --allowedTools "Read,Edit,Bash(git add *),Bash(git commit *)" \
    --permission-mode dontAsk \
    --max-turns 20
done
```

Run it on three files first, read the commits, fix the prompt, then run the rest. A run that hits the turn limit exits with an error, which your loop can log.

### A review script

```bash
gh pr diff "$1" | claude -p \
  --append-system-prompt "You are a security engineer. Review for vulnerabilities." \
  --output-format json
```

Save it as `review.sh` and run `bash review.sh 123`; the review text is in `result`.

### A nightly cron job

```bash
# crontab -e
0 2 * * * cd /srv/app && claude --bare -p "Run the test suite, fix failing tests, commit on the current branch. Do not push." --allowedTools "Read,Edit,Bash(npm test *),Bash(git add *),Bash(git commit *)" --permission-mode dontAsk --max-turns 30 --output-format json >> /var/log/claude-nightly.jsonl 2>> /var/log/claude-nightly.err
```

Cron runs with a minimal environment, so make sure `claude` is on its `PATH` and `ANTHROPIC_API_KEY` is set for that user. Run it on a branch or in a worktree, not on `main`. If you add `--worktree`, note that `-p --worktree` runs aren’t cleaned up for you; remove them with `git worktree remove <path>`. For the full overnight setup with budgets and morning review, see [How to run Claude Code overnight](/en/blog/run-claude-code-overnight/ "Run Claude Code overnight"), and for timers on each OS, [How to schedule Claude Code tasks](/en/blog/schedule-claude-code-tasks/ "Schedule Claude Code tasks").

### CI pipelines

In CI, use `--bare`, keep the API key in the CI secret store, and treat the exit code as the step result. For GitHub specifically, the official action wraps this for you, with PR comments and `@claude` mentions: see [Claude Code GitHub Actions](/en/blog/claude-code-github-actions/ "Claude Code GitHub Actions"). If you’re on a subscription rather than an API key, `claude setup-token` prints a long-lived OAuth token for CI and scripts; it isn’t saved, so store it as a secret right away. Remember that `--bare` ignores OAuth, so a subscription token needs a non-bare run.

## Pitfalls

- **Skills and slash commands.** User skills work if you put `/skill-name` in the prompt. Commands that only exist in the terminal interface, such as `/login`, don’t.
- **Silent tool denials.** If Claude says it couldn’t do something, check `permission_denials` in the JSON result and add the missing rule to `--allowedTools`.
- **Background work.** A `-p` run may stay open to wait for background commands or subagents Claude started; by default it stops waiting after 10 minutes of idle time.
- **Replacing the system prompt.** `--system-prompt` replaces the default prompt entirely, including the git status snapshot. Prefer `--append-system-prompt` unless you know you want a blank slate.

## When to move to the Agent SDK

`claude -p` is the Agent SDK’s engine behind a CLI. Once your script starts parsing streams, handling approvals or managing many sessions, switch to the Agent SDK for Python or TypeScript. It gives you native message objects, tool-approval callbacks and structured outputs in code, without shelling out. The [Claude Code best practices](/en/blog/claude-code-best-practices/ "Claude Code best practices") post covers what to put in place before any run goes unattended.

## Doing it with AgentVera

Headless runs are fire-and-forget; sometimes you want the same automation with a terminal you can watch. In AgentVera, [scheduled tasks](/en/features/scheduled-tasks/ "Scheduled tasks") (on every plan) send a prompt to an agent on a cron schedule while the app is open, and the agent runs in a real pane you can open and steer. [Automations](/en/features/automations/ "Automations") (Pro and Team) start an agent when CI goes red or a PR gets a comment, and never push to the default branch. The [CI/CD center](/en/features/ci-cd-center/ "CI/CD center") lists GitHub Actions, GitLab and Jenkins runs and can hand a failed log to an agent. [Night shift](/en/features/night-shift/ "Night shift") (Pro and Team) runs queued work overnight in a worktree on a `night/<date>-<slug>` branch, commits without pushing and leaves a morning report. [Download AgentVera](/en/download/ "Download AgentVera") if you want those runs visible instead of buried in a log file.

## Wrapping up

The core recipe for headless Claude Code is short: `claude --bare -p "clear task with a check"`, an explicit `--allowedTools` list, `--permission-mode dontAsk`, `--max-turns`, `--output-format json`, and an exit code your script respects. Start small, read what the first runs produce, and only then put it on a timer.

## Frequently asked questions

**What is Claude Code headless mode?** It is Claude Code run non-interactively with the -p (or --print) flag. You pass a prompt, Claude does the work with the tools you allow, prints the result to stdout and exits. Exit code 0 means success and a non-zero code means the run failed, so scripts can branch on it.

**How do I let claude -p edit files without prompting?** List the tools with --allowedTools, for example --allowedTools "Read,Edit,Bash(npm test *)". As of October 2026, -p runs start in Manual mode when nothing sets a mode, and tool calls that need approval are denied. Add --permission-mode dontAsk to make that explicit, or acceptEdits to allow all file edits.

**How do I get JSON output from Claude Code?** Add --output-format json. The response is one JSON object with the answer in result plus session_id, usage and a total_cost_usd estimate. Pipe it to jq -r ".result" for the text. Add --json-schema to get validated data in structured_output, and check the field exists before using it.

**Can I continue a headless Claude Code session?** Yes. claude -p "next step" --continue picks up the most recent conversation in the current directory. To target a specific one, capture session_id from a JSON run and pass it to --resume; Claude Code finds the session by ID from any directory on that machine.

**Does headless mode use my Claude subscription or an API key?** A normal -p run uses whatever login the machine has. With --bare, Claude Code never reads OAuth credentials or the keychain, so it needs ANTHROPIC_API_KEY or an apiKeyHelper in the --settings JSON. For CI on a subscription, claude setup-token generates a long-lived OAuth token.

---

**About AgentVera.** AgentVera is a desktop app for macOS, Windows and Linux, built by Solviera Teknoloji. It runs 18 AI coding agents (Claude Code, Codex, Gemini CLI, Copilot, Cursor, Antigravity, opencode, Hermes, Qwen Code, Kimi Code, Kiro, Amp, Droid, Goose, Vibe, Crush, Aider, Cline) side by side, per project, in real terminals with the user's own CLI installs and logins. The free plan has unlimited agents. Download: https://agentvera.dev/en/download/ · Pricing: https://agentvera.dev/en/pricing/
