CoreCodeBlog

Guides

claude -p guide: run Claude Code non-interactively in scripts and CI

Published · Last checked

This page shows how to call Claude Code from a script with claude -p: basic usage, piping input, the three output formats, tool permissions, resuming a conversation and bare mode, each with commands you can copy. Flags, fields and behavior come from Anthropic's Run Claude Code programmatically doc, checked on October 30, 2026. It is a selection; the doc covers more, such as subagent messages and background work.

Key points
  • Pass -p (or --print) with a prompt to run non-interactively. Claude Code exits with code 0 on success and a non-zero code when the run fails, so a script can branch on it.
  • --output-format takes text (default), json or stream-json. Add --json-schema to get a structured_output field.
  • In a Manual mode claude -p run, a tool call that needs approval is denied unless something approves it. List tools with --allowedTools or set a baseline with --permission-mode.
  • Anthropic recommends --bare for scripted and SDK calls: it skips auto-discovery of hooks, skills, plugins, MCP servers, auto memory and CLAUDE.md, so the result is the same on every machine.

What -p does

Add the -p (or --print) flag to any claude command to run it non-interactively. This runs one prompt and prints the response:

claude -p "What does the auth module do?"

Not every CLI option combines with -p. Claude Code rejects --bg, and rejects --cloud with a task description, with an error naming the conflict.

Claude Code exits with code 0 on success and a non-zero code when the run fails. If you pass an invalid flag, it reports the error to stderr before the run starts. When a failure happens inside the run, such as missing authentication, it prints the failure as the result on stdout.

You can combine -p with other flags. This one pre-approves three tools so Claude can work without a prompt:

claude -p "Find and fix the bug in auth.py" --allowedTools "Read,Edit,Bash"

Piping input

Non-interactive mode reads stdin, so you can pipe data in and redirect the response out like any other command-line tool. This pipes a build log in and writes the explanation to a file:

cat build-error.txt | claude -p 'concisely explain the root cause of this build error' > output.txt

Piped stdin is capped at 10MB. If you exceed the cap, Claude Code exits with a clear error and a non-zero status. For larger inputs, write the content to a file and reference the file path in your prompt instead. If Claude Code can't read stdin, for example because the process that started it disconnected its end, it prints a warning to stderr and continues with the prompt from the command line.

You can also wrap a call in a script and use Claude as a project-specific linter. This package.json script pipes the diff against main into Claude and asks it to report typos. Piping the diff means Claude doesn't need Bash permission to read it, and the escaped double quotes keep the script portable to Windows:

{
  "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.\""
  }
}

Run it with npm run lint:claude.

Output formats

Use --output-format to control how responses are returned:

ValueWhat you get
text (default)Plain text output
jsonStructured JSON with result, session ID, and metadata
stream-jsonNewline-delimited JSON for real-time streaming

This returns a project summary as JSON, with the text result in the result field:

claude -p "Summarize this project" --output-format json

With --output-format json, the response payload also includes total_cost_usd and a per-model cost breakdown, so a script can track spend. When you continue an earlier conversation with --continue or --resume, the run reports the conversation's whole total, earlier runs' spend included. Both figures are client-side estimates and can differ from your actual bill.

Use a tool like jq to pull out specific fields:

# Extract the text result
claude -p "Summarize this project" --output-format json | jq -r '.result'

# Extract structured output
claude -p "Extract function names from auth.py" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \
  | jq '.structured_output'

Structured output with a JSON Schema

To get output conforming to a specific schema, use --output-format json with --json-schema and a JSON Schema definition. The response includes metadata about the request (session ID, usage, etc.) with the structured output in the structured_output field. This extracts function names and returns them as an array of strings:

claude -p "Extract the main function names from auth.py" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}'

If the value isn't a valid JSON Schema, claude exits with Error: --json-schema is not a valid JSON Schema followed by the validator's diagnostic. Claude Code accepts schemas that use the format keyword, such as "format": "email", but treats format as an annotation and doesn't enforce it. If a run ends without valid structured output, for example because Claude declined the request, the response has no structured_output field, so check that the field is present before your script uses it.

Streaming

Use --output-format stream-json with --verbose and --include-partial-messages to receive tokens as they're generated. Each line is a JSON object representing an event:

claude -p "Explain recursion" --output-format stream-json --verbose --include-partial-messages

The last line of the stream is a result message with the final response text, cost, and session metadata. This jq filter keeps only the text deltas; -r outputs raw strings (no quotes) and -j joins without newlines so tokens stream continuously:

claude -p "Write a poem" --output-format stream-json --verbose --include-partial-messages | \
  jq -rj 'select(.type == "stream_event" and .event.delta.type? == "text_delta") | .event.delta.text'

The system/init event reports session metadata including the model, tools, MCP servers, and loaded plugins. When an API request fails with a retryable error, Claude Code emits a system/api_retry event before retrying; its fields include attempt, max_retries, retry_delay_ms and error_status. With stream-json, denials appear as permission_denied system messages, and the final result message lists them in permission_denials.

Tool permissions in non-interactive mode

Nobody is at the keyboard to answer a prompt, so decide up front what Claude may do. Use --allowedTools to let Claude use certain tools without prompting. Listing Read and Edit lets Claude read and edit files without asking for permission, apart from reads from network paths. Listing Bash does the same for shell commands, except in a run that starts in auto mode, where Claude Code drops a bare Bash entry as a broad allow rule and auto mode evaluates each command instead. This runs a test suite and fixes failures with those three tools listed:

claude -p "Run the test suite and fix any failures" \
  --allowedTools "Bash,Read,Edit"

Calls to tools you didn't list run if they need no approval. In a Manual mode claude -p run, a call that needs approval is denied unless something approves it, such as a PermissionRequest hook or a --permission-prompt-tool. With Claude Code v2.1.302 or later, claude -p runs start in Manual mode when nothing sets a permission mode.

To limit a tool to specific commands, --allowedTools uses permission rule syntax. The trailing * enables prefix matching, so Bash(git diff *) allows any command starting with git diff. The space before * is important: without it, Bash(git diff*) would also match git diff-index.

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 *)"

To set a baseline for the whole session instead of listing individual tools, pass a permission mode with --permission-mode:

ModeBehavior in a -p run
autoA classifier reviews most actions instead of you
dontAskClaude Code denies every call that would otherwise prompt, which is useful for locked-down CI runs. Actions that need no approval in Manual mode still run, and so do actions covered by your --allowedTools entries or permissions.allow rules
acceptEditsClaude writes files without prompting, and Claude Code auto-approves common filesystem commands such as mkdir, touch, mv, and cp. Other shell commands and network requests still need an --allowedTools entry or a permissions.allow rule
claude -p "Apply the lint fixes" --permission-mode acceptEdits

For unattended jobs, pass --permission-prompts none when nobody is available to answer permission prompts, for example in a scheduled job. Anything that would prompt is denied unless a PermissionRequest hook allows it, Claude is told that nobody can approve the request and not to retry it, and the run continues. The flag requires Claude Code v2.1.259 or later; earlier versions reject it with an unknown-option error.

claude -p "Update the dependency pins and run the tests" --permission-mode auto --permission-prompts none

The modes themselves are explained in Claude Code permission modes.

Continuing a conversation

Use --continue to continue the most recent conversation, or --resume with a session ID to continue a specific conversation. This runs a review, then sends follow-up prompts:

# First request
claude -p "Review this codebase for performance issues"

# Continue the most recent conversation
claude -p "Now focus on the database queries" --continue
claude -p "Generate a summary of all issues found" --continue

If you're running multiple conversations, capture the session ID to resume a specific one:

session_id=$(claude -p "Start a review" --output-format json | jq -r '.session_id')
claude -p "Continue that review" --resume "$session_id"

You can run the two commands from different directories: Claude Code finds the session by its ID in any project on this machine. In place of the session ID, you can pass --resume the absolute path to a session's .jsonl transcript file, and Claude Code continues the conversation stored in that file.

Bare mode for scripts and CI

Add --bare to reduce startup time by skipping auto-discovery of hooks, skills, custom commands, subagents, installed plugins, MCP servers, auto memory, and CLAUDE.md. Without it, claude -p discovers and loads them as an interactive session would, including anything configured in the working directory or ~/.claude. Bare mode is useful for CI and scripts where you need the same result on every machine: a hook in a teammate's ~/.claude or an MCP server in the project's .mcp.json won't run, because bare mode never reads them.

Set ANTHROPIC_API_KEY before running this, because bare mode doesn't use your subscription login. It pre-approves the Read tool so the call completes without a permission prompt:

claude --bare -p "Summarize README.md" --allowedTools "Read"

In bare mode, Claude Code never reads OAuth credentials or the system keychain. For the Anthropic API, set ANTHROPIC_API_KEY in the environment, with a key created in the Claude Console, or supply an apiKeyHelper in the --settings JSON. Amazon Bedrock, Google Cloud's Agent Platform, and Microsoft Foundry continue to read their own provider credentials as usual.

In bare mode Claude has access to the Bash, file read, and file edit tools. Pass any context you need with a flag:

To loadUse
System prompt additions--append-system-prompt, --append-system-prompt-file
Settings--settings <file-or-json>
MCP servers--mcp-config <file-or-json>
Custom agents--agents <file-or-json>
A plugin--plugin-dir <path>, --plugin-url <url>

Without --bare, a -p session runs the hooks in a project's .claude/settings.json and connects the servers in its .mcp.json, even in a folder you've never trusted. A -p session shows no workspace trust dialog and no per-server approval prompt. Keep that in mind before you run claude -p in a repository you did not write. The doc says --bare is the recommended mode for scripted and SDK calls, and will become the default for -p in a future release.

Other recipes

Use --append-system-prompt to add instructions while keeping Claude Code's default behavior. This pipes a PR diff to Claude and asks for a security review. Save it as a shell script, for example review.sh, and run bash review.sh 123; the shell replaces "$1" with 123:

gh pr diff "$1" | claude -p \
  --append-system-prompt "You are a security engineer. Review for vulnerabilities." \
  --output-format json

--system-prompt fully replaces the default prompt. If you replace it, the run also starts without the git status snapshot.

Command support differs in -p mode. User-invoked skills and custom commands work: include /skill-name in the prompt string and Claude Code expands it before running. Built-in commands that only run in the terminal interface, such as /login, aren't available. To change a setting, pass key=value to /config, for example /config thinking=false. For the interactive commands, see Claude Code commands. To run claude -p in GitHub workflows, see Claude Code with GitHub Actions.

Troubleshooting

SymptomWhat the doc says
An invalid flagClaude Code reports the error to stderr before the run starts
Missing authenticationA failure inside the run, so Claude Code prints it as the result on stdout
Authentication fails with --bareBare mode doesn't use your subscription login and never reads OAuth credentials or the system keychain. Set ANTHROPIC_API_KEY or supply an apiKeyHelper in the --settings JSON
An error naming a conflict--bg is rejected with -p, and so is --cloud with a task description
A tool call is deniedIn a Manual mode run, a call that needs approval is denied unless something approves it. Add it to --allowedTools or set --permission-mode
An unknown-option error on --permission-promptsThe flag requires Claude Code v2.1.259 or later
Error: --json-schema is not a valid JSON SchemaThe value you passed isn't a valid JSON Schema; the validator's diagnostic follows the message
No structured_output fieldThe run ended without valid structured output, for example because Claude declined the request. Check that the field is present first
Piping a very large file failsPiped stdin is capped at 10MB. Write the content to a file and put the path in the prompt
A configured MCP server is missingClaude Code skips --mcp-config entries that fail validation, and the run exits cleanly. Check mcp_server_errors in the system/init event; the key is omitted when there are no errors, so a CI gate can fail on a non-empty array
The run stays open after Claude finishesIt can wait for background work that Claude started. By default Claude Code stops whatever is still running after 10 minutes of continuous idle waiting. Set CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS to change the cap, or to 0 to wait without one
Exit code 143You stopped the run with SIGTERM. Claude Code leaves the turn in progress unfinished and records no result for it. To end the turn instead, send SIGINT before you stop the process. To continue an interrupted turn on resume, set CLAUDE_CODE_RESUME_INTERRUPTED_TURN=1

FAQ

Is -p the same as --print?

Yes. The doc describes the flag as -p (or --print), and it turns any claude command into a non-interactive run.

Why was my edit or command denied in a script?

Nobody can answer a permission prompt in a -p run. In Manual mode, a call that needs approval is denied unless something approves it, such as a PermissionRequest hook or a --permission-prompt-tool. List the tools you want with --allowedTools, for example --allowedTools "Bash,Read,Edit", or pass --permission-mode acceptEdits for file edits.

How do I get the same result on every machine or CI runner?

Add --bare. It skips hooks, skills, plugins, MCP servers, auto memory and CLAUDE.md from the host, so a teammate's ~/.claude or a project's .mcp.json can't change the run. Set ANTHROPIC_API_KEY first, since bare mode doesn't use your subscription login, and pass any context you need with flags such as --settings or --mcp-config.

Sources