So far Claude Code has worked in a dialogue: you ask, it answers, you approve. But sometimes you need Claude with nobody on the other end — in a script, on a schedule, in CI. That is what is for.
One request, one run
claude -p "In one sentence: what does this repository do?"
Claude Code runs the request, prints the answer and exits: with code 0 if everything went fine, non-zero on failure. It also reads stdin, so a script can feed it, for example, a diff:
git diff | claude -p "Find obvious mistakes in this diff" --permission-mode dontAsk
An answer you can parse
With the --output-format json flag you get a single JSON object instead of text:
{"type":"result","subtype":"success","is_error":false,"num_turns":2,"result":"…","session_id":"…","permission_denials":[]}
result— the answer itself;is_error— whether there was an error;subtype—successor the reason for an early stop, such aserror_max_turns;num_turns— how many turns it took;permission_denials— which actions were denied.
Important: if the run stopped at a limit, there is no result field at all. So check the exit code and is_error first, and only then take the answer.
Always set the mode explicitly
In a script, nobody can answer a permission prompt. So the mode is passed every time: without the flag, the run takes permissions.defaultMode from your settings files — which can be auto — and with no mode set anywhere it starts in Manual, where every call that needs approval is denied.
Three flags set the limits:
--permission-mode dontAsk— denies anything that would have needed a question in a session;--allowedTools— lists what is allowed after all, for example"Bash(npm test *)";--max-turns— caps the number of turns.
claude -p "Run the tests and tell me how many passed" \
--output-format json --permission-mode dontAsk \
--allowedTools "Bash(npm test *)" --max-turns 3
The space before * matters: Bash(npm test *) allows npm test with any arguments, including running it with none.
Quick check
A script calls claude -p and immediately takes the result field from the JSON. Sometimes it crashes with 'result is undefined'. Why?
Careful
claude -p does not show the trust dialog: it runs the hooks from the project’s .claude/settings.json and connects the servers from .mcp.json even in a folder you have never trusted. When you run Claude from a script in someone else’s repository, add --setting-sources user — then the project’s settings and MCP servers are not loaded. And remember: if the ANTHROPIC_API_KEY variable is set, such a run spends that key, not your subscription.
Practice in the terminal