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:
| Command | Default | --permission-mode dontAsk | --permission-mode acceptEdits |
|---|---|---|---|
ls | Ran | Ran | Ran |
git status | Ran | Ran | Ran |
cat a.txt | Ran | Ran | Ran |
npm test | Refused: "requires approval" | Denied | Refused: "requires approval" |
mkdir made-by-claude | Blocked, nothing created | Denied | Ran, folder created |
echo new > b.txt | Blocked, nothing created | Denied | Ran, file created |
- Read-only commands always ran.
ls,git statusandcatneed no permission in any mode we tried. - Default: anything that changes state was refused or blocked.
dontAskis the locked-down option for CI. Everything that would have prompted is denied outright, with a message saying Claude Code is in "don't ask mode".acceptEditslets Claude create folders and write files without asking, but a command likenpm testwas still refused. The documentation says other shell commands and network requests still need approval.
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
- Allow rules in a project's
.claude/settings.jsonare ignored. Claude Code applies them only after you accept the trust dialog, and-pnever shows it. Put them in.claude/settings.local.json, or pass them on the command line. - Deny rules and hooks still apply. Our guard hooks and deny rules worked in every
-ptest. Claude Code's documentation adds that without--bare, a-prun also connects the servers in the project's.mcp.jsonwithout asking, even in a folder you have never trusted. Do not runclaude -pin a repository you did not write without thinking about that.
Tips for testing safely
- Use
--model haikuand--max-budget-usdwhile experimenting. - Add
--no-session-persistenceso test runs are not saved as sessions. - Test in a scratch folder, and use
--disallowedToolsto take away tools a test should not touch. - The model may decline a destructive-looking prompt by itself, and then your guardrail is never tested. Tell it the folder is a throwaway one. We used this approach to test a git push guard and the hooks in this guide series.