Run Claude Code from a script: claude -p, cost caps and permissions

The -p flag runs Claude Code once, without a chat window, and prints the answer. It is how you use Claude Code in scripts, scheduled jobs and CI. Because nobody is there to click "approve" or watch the bill, three things matter: what the output looks like, how the spending cap behaves, and which commands are allowed to run. We tested all three in Claude Code 2.1.278 on Windows in September 2026, using the cheap Haiku model, and report what actually happened.

The short version: read is_error and the exit code, not just the text. A budget cap stops the run but can be overshot. And a shell command that needs approval is simply refused, so decide in advance what to allow.

The basics

claude -p "Summarize what package.json says about this project"

Claude Code exits with code 0 on success and non-zero when the run fails, so a script can branch on it. Add --output-format json for one JSON object, or --output-format stream-json --verbose to watch each step as it happens. The stream form is what shows you every tool call and its result, which is how we saw the results below.

What the JSON contains

For a one-word answer we got these fields back, among others: result (the text), is_error, subtype (success when it worked), num_turns, total_cost_usd, session_id, usage and permission_denials. With the jq tool, claude -p "..." --output-format json | jq -r .result pulls out just the text.

What a run costs

Even a trivial request has a floor. Asking Haiku to reply with one word cost about $0.03, and our tiny test runs cost between $0.02 and $0.06 each. Claude Code sends its instructions and your project context with every run, so cost does not shrink to zero for short prompts. The documentation describes a --bare flag that skips loading hooks, skills, CLAUDE.md and other project context to start faster. It needs an API key rather than your subscription login, so we did not test it.

The budget cap (tested)

--max-budget-usd sets a spending limit for the run. We set it far too low on purpose, at $0.0001, and asked for two haiku poems:

exit code: 1
subtype: error_max_budget_usd
is_error: true
result: ""
total_cost_usd: 0.024

Two lessons. First, the run failed cleanly: exit code 1, is_error true, and a subtype that names the cause, so a script can detect it. Second, the run still cost $0.024, about 240 times the cap. The limit is checked between steps, so the step in flight finishes first. Treat the cap as a safety net that stops a runaway loop, not as an exact ceiling, and set it a little below what you can afford.

Which commands run without approval (tested)

In a normal chat, Claude Code asks before it runs most shell commands. In -p mode there is nobody to ask, so a command that would need approval is refused. We gave Claude the same six commands in a scratch project under three permission modes, then listed the folder to see what really happened:

CommandDefault--permission-mode dontAsk--permission-mode acceptEdits
lsRanRanRan
git statusRanRanRan
cat a.txtRanRanRan
npm testRefused: "requires approval"DeniedRefused: "requires approval"
mkdir made-by-claudeBlocked, nothing createdDeniedRan, folder created
echo new > b.txtBlocked, nothing createdDeniedRan, file created

Approving specific tools

--allowedTools pre-approves tools for the run. Be careful how broadly you use it. In our tests, --allowedTools "Bash" allowed every shell command, and in a control run a git push was attempted. Prefer a narrow rule such as --allowedTools "Bash(npm test *)". We tried exactly that: npm test ran and npm --version was refused. The rule syntax and its traps are covered in the allow-rules guide.

Project settings behave differently in -p

Tips for testing safely

Allow rules that work Safe setup pack