How to Run Claude Code Headless in a Script or CI
Run Claude Code non-interactively with claude -p, get JSON back, pre-approve tools, and wire it into a CI job. Copy-paste.
Step 5 of 6 · Try the other coding agents
On this page4 sections
claude -p runs a single prompt and exits. No TUI, no approval prompts to click — which is
exactly what a script or a CI job needs.
1. One prompt, one answer
claude -p "What does the auth module do?"
-p (long form --print) is the whole trick. Claude Code exits with code 0 on success and
non-zero when the run fails, so your script can branch on $?.
It also reads stdin, so it composes like any other command-line tool:
cat build-error.txt | claude -p 'concisely explain the root cause of this build error' > output.txt
2. Pre-approve the tools it needs
Without permission to act, a headless run can only talk. --allowedTools grants tools up front:
claude -p "Run the test suite and fix any failures" \
--allowedTools "Bash,Read,Edit"
You can scope Bash down to specific commands using permission rule syntax:
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 git diff …, while Bash(git diff*)
would also match git diff-index.
3. Get JSON out
claude -p "Summarize this project" --output-format json | jq -r '.result'
--output-format takes text (default), json (result plus session ID, cost, and metadata),
or stream-json (newline-delimited events). The JSON payload includes total_cost_usd, so a
script can log spend per invocation.
4. Harden it for CI
Add --bare. It skips auto-discovery of hooks, skills, custom commands, subagents, plugins,
MCP servers, and CLAUDE.md, so the run behaves the same on every machine instead of picking
up whatever is in the repo or a teammate’s ~/.claude. Bare mode doesn’t use your subscription
login, so set ANTHROPIC_API_KEY:
claude --bare -p "Summarize README.md" --allowedTools "Read"
A wrapper in package.json turns this into a project-specific reviewer:
{
"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.\""
}
}
Tip: in an unattended job, nobody can answer a permission prompt. Pass
--permission-prompts none so anything that would prompt is denied and the run continues
instead of hanging.
Next: set up the Claude Code GitHub Action or tighten Claude Code permissions.