Run claude -p "your prompt" to execute one task and print the result. It exits with 0 on success and non-zero on failure. Add --output-format json to get result, session_id and total_cost_usd, --allowedTools "Read,Edit,Bash" so nothing waits for approval, and --bare in CI so the run ignores the host's hooks, plugins and CLAUDE.md. For GitHub, run /install-github-app or use anthropics/claude-code-action@v1.
What is Claude Code headless mode?
"Headless" means running Claude Code without the interactive interface. You pass -p (or --print) and a prompt. Claude does the work, prints the answer and exits. The official docs now describe this as the Agent SDK used through the CLI, with the same tools and agent loop as the interactive app.
claude -p "What does the auth module do?"
# stdin works too, so Claude fits into pipes
cat build-error.txt | claude -p 'concisely explain the root cause of this build error' > output.txt
Piped input is capped at 10MB. For larger inputs, write a file and name its path in the prompt. A failure inside the run, such as missing authentication, is printed as the result on stdout, so check the exit code in scripts. If you stop a run with SIGTERM, it exits with code 143 and records no result for the unfinished turn.
You can wrap it in a build script. This one reviews the diff against main for typos. Piping the diff means Claude needs no Bash permission to read it:
{
"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.\""
}
}
How do I get JSON or structured output?
--output-format takes three values: text (default), json and stream-json. The JSON form carries the text in result plus session metadata. With --output-format json, the payload also includes total_cost_usd, which is a client-side estimate and can differ from your bill.
claude -p "Summarize this project" --output-format json | jq -r '.result'
# Force a shape with a JSON Schema
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"]}' \
| jq '.structured_output'
The schema-checked data lands in structured_output. If a run ends without valid structured output, for example because Claude declined, that field is missing, so test for it before your script uses it. For token streaming, use --output-format stream-json --verbose --include-partial-messages and filter text deltas with jq. The last line of the stream is a result message with the final text and cost.
How do I stop a headless run from hanging on permission prompts?
Nobody is there to click "Yes". In a Manual-mode -p run, a call that needs approval is denied unless a PermissionRequest hook or a permission prompt tool approves it. So decide up front what Claude may do. The first tool is --allowedTools, which uses the same rule syntax as settings.json:
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 trailing space before * matters. Bash(git diff *) allows any command starting with git diff, while Bash(git diff*) would also match git diff-index. For the full rule syntax, see our permissions guide.
The second tool is a permission mode for the whole run:
| Flag | Use it for |
|---|---|
--permission-mode acceptEdits | File edits and common filesystem commands. Other shell commands still need an allow rule. |
--permission-mode dontAsk | Locked-down CI. Anything that would prompt is denied, so the run never waits. |
--permission-mode auto | A classifier reviews most actions instead of you. |
--permission-prompts none | Unattended jobs with a permission host. Denies whatever would prompt and tells Claude not to retry. Needs v2.1.259 or later. |
A combination that works for a scheduled job: claude -p "Update the dependency pins and run the tests" --permission-mode auto --permission-prompts none. With stream-json, denials show up as permission_denied messages, and the final result lists them in permission_denials.
Should I use --bare in CI?
Yes. Without it, claude -p discovers and loads hooks, skills, plugins, MCP servers, auto memory and CLAUDE.md from the working directory and ~/.claude, just like an interactive session. That includes the project's .claude/settings.json hooks and .mcp.json servers, with no trust dialog, even in a folder you never opened before. Hooks and MCP servers committed to the repo run in CI too, which can change results.
export ANTHROPIC_API_KEY=...
claude --bare -p "Summarize README.md" --allowedTools "Read"
--bare skips all of that, so every machine behaves the same. It never reads OAuth credentials or the keychain, so it needs ANTHROPIC_API_KEY (or an apiKeyHelper passed through --settings). Bedrock, Vertex and Foundry keep using their own credentials. To pass context back in, use --append-system-prompt, --settings, --mcp-config or --agents. Hooks passed through --settings still do not run in bare mode, so skip bare mode if the run needs hooks. The docs call --bare the recommended mode for scripted calls and say it will become the default for -p in a future release.
How do I cap cost and turns?
--max-turns 3limits agentic turns in print mode. There is no limit by default.--max-budget-usd 2.00stops the run once estimated spend reaches the amount. It uses the same client-side estimate astotal_cost_usd, and spend can pass the cap, so leave headroom.- Subagent spend counts toward the budget cap.
To continue earlier work, use --continue for the most recent conversation, or capture the session ID:
session_id=$(claude -p "Start a review" --output-format json | jq -r '.session_id')
claude -p "Continue that review" --resume "$session_id"
How do I run Claude Code in GitHub Actions?
The fast path: open claude in the repository and run /install-github-app. It installs the Claude GitHub App, stores a secret and opens a pull request with the workflow files. It works only for github.com repositories, needs the GitHub CLI (gh auth login) and requires admin access to the repo.
For manual setup, install the app, add a repository secret, and copy a workflow. The secret is either ANTHROPIC_API_KEY (a Console API key) or CLAUDE_CODE_OAUTH_TOKEN (a subscription token, generated with claude setup-token). This workflow, from the docs, answers @claude mentions in issues and pull requests:
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
jobs:
claude:
if: contains(github.event.comment.body, '@claude')
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
When you add a prompt input, the action switches to automation mode and runs on any event without waiting for a mention. Pass CLI flags through claude_args. This schedule runs a report every morning and caps the run at five turns:
name: Daily Report
on:
schedule:
- cron: "0 9 * * *"
jobs:
report:
runs-on: ubuntu-latest
permissions:
contents: read
issues: read
id-token: write
steps:
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: "Generate a summary of yesterday's commits and open issues"
claude_args: |
--max-turns 5
--allowedTools "mcp__github__list_commits,mcp__github__list_issues"
For a plain-text prompt, Claude has no shell or GitHub API access until you grant tools with --allowedTools, as above. Runs also use GitHub Actions minutes, so add workflow timeouts and concurrency limits next to --max-turns. Keep CLAUDE.md short, since Claude reads it on every run.
Problems you will hit
- CI does not run on Claude's commits. Commits made with the default
GITHUB_TOKENdo not trigger workflows. Do not passgithub_tokento the action, so it authenticates as the Claude GitHub App. - Fork pull requests fail. On public repositories GitHub withholds secrets from fork PR runs, so a review workflow works only for branches in the same repository.
- Nothing happens on a mention. The comment must contain
@claudeas a whole word, and the commenter needs write access. - Old workflows. If yours still says
@beta, switch to@v1, replacedirect_promptwithpromptand move options likemax_turnsintoclaude_args.
FAQ
Do slash commands work with claude -p?
User-invoked skills and custom commands do: put /skill-name in the prompt and Claude Code expands it. Terminal-only built-ins such as /login are not available.
Does claude -p use my subscription or the API?
A normal -p run uses whatever login the machine has. --bare never reads OAuth credentials and needs ANTHROPIC_API_KEY. The GitHub Action accepts either an API key or a subscription OAuth token.
Can I turn off session saving?
Yes. --no-session-persistence works with --print, but those sessions cannot be resumed.
Where do I find the full list of flags?
Run claude --help or open the CLI reference. Not every flag combines with -p: --bg is rejected.
Sources
Checked against the official docs and the local CLI (claude --help) on November 10, 2026:
Run Claude Code programmatically,
CLI reference and
Claude Code GitHub Actions. The --max-turns flag is documented in the CLI reference but does not appear in the local --help output of v2.1.304.
See every current command, flag, and setting in one reference.