obsidian/wiki/claude-code/headless-cli.md
2026-04-17 13:08:46 +01:00

150 lines
5 KiB
Markdown

---
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`