150 lines
5 KiB
Markdown
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`
|