---
title: "Run Claude Code Programmatically (CLI / Headless)"
aliases: [headless-mode, claude-p-flag, programmatic-claude]
tags: [claude-code, cli, headless, automation, ci-cd, agent-sdk]
sources: [raw/Run Claude Code programmatically.md]
created: 2026-04-17
updated: 2026-04-17
---

# Run Claude Code Programmatically (CLI / Headless)

The `-p` (or `--print`) flag runs Claude Code non-interactively. It was previously called "headless mode." All CLI options work with `-p`.

For structured outputs, tool-approval callbacks, and native message objects use the full [[wiki/agent-sdk/_index|Agent SDK]] packages instead.

---

## Basic Usage

```bash
claude -p "What does the auth module do?"
claude -p "Find and fix the bug in auth.py" --allowedTools "Read,Edit,Bash"
```

---

## Bare Mode (`--bare`)

Skips auto-discovery of hooks, skills, plugins, MCP servers, auto memory, and CLAUDE.md — so every machine gives the same result.

```bash
claude --bare -p "Summarize this file" --allowedTools "Read"
```

- **Use for CI/scripts** — teammate's `~/.claude` hooks or project `.mcp.json` won't interfere
- Auth must come from `ANTHROPIC_API_KEY` or `apiKeyHelper` in `--settings` JSON (no OAuth/keychain)
- In bare mode, Claude has Bash, file read, and file edit tools by default
- `--bare` will become the default for `-p` in a future release

| Need | Flag |
|------|------|
| System prompt additions | `--append-system-prompt` / `--append-system-prompt-file` |
| Settings | `--settings <file-or-json>` |
| MCP servers | `--mcp-config <file-or-json>` |
| Custom agents | `--agents <json>` |
| Plugin directory | `--plugin-dir <path>` |

---

## Structured Output

```bash
# JSON with session metadata; result in `.result` field
claude -p "Summarize this project" --output-format json

# Schema-constrained output; result in `.structured_output` field
claude -p "Extract function names from auth.py" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}'

# Parse with jq
claude -p "Summarize" --output-format json | jq -r '.result'
```

Output format options:
- `text` (default) — plain text
- `json` — structured JSON with result, session ID, metadata
- `stream-json` — newline-delimited JSON for real-time streaming

---

## Streaming Responses

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

Key stream events:
- `system/init` — session metadata: model, tools, plugins loaded. Use `plugin_errors` field to fail CI on bad plugin load
- `system/api_retry` — emitted before each retry; fields: `attempt`, `max_retries`, `retry_delay_ms`, `error_status`, `error`
- `system/plugin_install` — emitted when `CLAUDE_CODE_SYNC_PLUGIN_INSTALL` is set; statuses: `started`, `installed`, `failed`, `completed`

---

## Auto-Approve Tools

```bash
# Approve specific tools
claude -p "Run tests and fix failures" --allowedTools "Bash,Read,Edit"

# Permission mode — broader baseline
claude -p "Apply lint fixes" --permission-mode acceptEdits
```

- `acceptEdits` — auto-approves file writes plus `mkdir`, `touch`, `mv`, `cp`
- `dontAsk` — denies anything not in `permissions.allow` or the read-only command set (useful for locked-down CI)
- `--allowedTools` uses permission rule syntax: `Bash(git diff *)` — note the space before `*` for prefix matching

---

## Continue Conversations

```bash
# Continue most recent conversation
claude -p "Review codebase for perf issues"
claude -p "Now focus on database queries" --continue

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

---

## Customize System Prompt

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

- `--append-system-prompt` — adds to default behavior
- `--system-prompt` — fully replaces default prompt

---

## Limitations

- [[wiki/claude-code/skills|Skills]] (`/commit`, etc.) and built-in commands are **only available in interactive mode** — describe the task in plain language instead
- [[wiki/claude-code/agent-teams|Agent teams]] and channels require interactive or SDK sessions

---

## Key Takeaways

- Use `-p` for any non-interactive/scripted Claude Code call
- Always add `--bare` in CI — prevents local config bleed and speeds up startup
- `--output-format json` + `jq` is the idiomatic way to extract structured results
- `--allowedTools` and `--permission-mode` are the two levers for controlling what Claude can do without prompting
- Capture `session_id` from JSON output to resume multi-step pipelines
- For Python/TypeScript with callbacks and typed messages, use the full [[wiki/agent-sdk/_index|Agent SDK]]

---

## Sources

- `raw/Run Claude Code programmatically.md`
