Run Claude Code from a script with claude -p
Add -p to a claude command and it answers once, prints the result and exits. That makes it usable inside shell scripts, build steps and scheduled jobs. The catch: nobody is there to click yes, so you have to decide in advance what it may do.
The basics
claude -p "What does the auth module do?"
Claude Code exits with code 0 when the run succeeds and a non-zero code when it fails, so a script can branch on the exit status. It also reads standard input, so you can pipe text in and send the answer to a file:
cat build-error.txt | claude -p 'concisely explain the root cause of this build error' > output.txt
Piped input is capped at 10 MB. For anything bigger, write it to a file and mention the file path in your prompt.
Choose the output format
text(default)Plain text, for people or for a file.jsonOne JSON object with the answer inresult, plus the session ID and metadata, includingtotal_cost_usdso a script can track spend.stream-jsonOne JSON event per line as it happens. Needs--verbose, and--include-partial-messagesfor tokens as they arrive.
claude -p "Summarize this project" --output-format json | jq -r '.result'
To get an answer in a shape you define, add --json-schema with a JSON Schema. The data comes back in the structured_output field. If a run ends without valid structured output, for example because Claude declined, that field is missing, so check that it is there before you use it.
What it may do when nobody can click yes
In a claude -p run in the default Manual mode, a tool call that would normally ask for approval is denied unless something approves it. You have three simple ways to say yes ahead of time:
- Allow specific tools:
--allowedTools "Read,Edit". ListingBashallows shell commands, and you can narrow it with the same pattern rules as in settings:"Bash(git diff *),Bash(git status *)". The trailing*matches any command that starts with those words, and the space before the star matters, becauseBash(git diff*)would also matchgit diff-index. - Pick a mode:
--permission-mode acceptEditslets Claude write files without asking.dontAskdenies everything that is not pre-approved, which suits locked-down jobs.autohands the decision to a classifier. See the permission modes explained. - No prompts at all:
--permission-prompts nonedenies anything that would have asked and tells Claude that nobody can approve it, so it does not keep retrying.
A starting list of rules is in a starter list of permission rules. Deny rules still apply in a script run.
Bare mode, and why it matters
By default a -p run loads what an interactive session would: hooks, skills, subagents, plugins, MCP servers, auto memory and CLAUDE.md. Add --bare and it skips all of that. You then pass in only what you want, for example --settings, --mcp-config or --append-system-prompt.
claude --bare -p "Summarize README.md" --allowedTools "Read"
Two things to know. First, bare mode does not use your subscription login: it needs ANTHROPIC_API_KEY or another provider's credentials. Second, the documentation recommends bare mode for scripts and CI, because the result no longer depends on whatever hooks or servers happen to be on that machine.
One warning about repositories you did not write
Without --bare, a -p run executes the hooks in the project's .claude/settings.json and connects the servers in its .mcp.json, even in a folder you have never trusted. Print mode shows no trust dialog and no per-server approval prompt. So before you point a script at a repository you did not write, read those two files, or use --bare.
Keep a conversation going
claude -p "Review this codebase for performance issues"
claude -p "Now focus on the database queries" --continue
--continue picks up the most recent conversation. To resume a specific one, capture its ID:
session_id=$(claude -p "Start a review" --output-format json | jq -r '.session_id')
claude -p "Continue that review" --resume "$session_id"
Good to know
- Commands: skills you invoke by name work if you put
/skill-namein the prompt. Commands that only exist in the terminal interface, such as/login, are not available. - Stopping a run: SIGTERM exits with code 143 and leaves the turn in progress unfinished. Send SIGINT first if you want the turn to end cleanly.
- Spending: the documentation mentions a
--max-budget-usdoption that caps a run's spend. Check the CLI reference for how it works in your version. - Not every option combines with
-p. Claude Code rejects some with an error that names the conflict.
Try it on something harmless
- Run
echo "hello" | claude -p "reply with one word"and check that you get an answer. - Run the same with
--output-format jsonand look at the fields. - Run
echo $?right after (orecho %errorlevel%on Windows cmd) and confirm 0.
Limits
- Flags and defaults change between versions, and several details on the documentation page need a minimum version. Check yours with
claude --version. - This covers the command line. The Python and TypeScript packages have their own documentation.
This follows Anthropic's documentation on running Claude Code programmatically as we read it in October 2026. We are not affiliated with Anthropic. We wrote this from the documentation and have not tested every example.
See also: Run Claude on a schedule with /loop · Work toward a goal with /goal · Everyday workflows · Claude Code in GitHub Actions.
The free loop-stop skill gives long tasks a limit and a stop rule. If you want a second pair of eyes on your CLAUDE.md and settings, the Tune-Up Kit is a checklist for exactly that.