Claude Code is built for interactive use, but the same agent loop runs perfectly well without a terminal — read a prompt, run tools, write output, exit. This is "headless" or "print" mode, controlled by the -p flag. It's what you use when Claude Code runs in CI, in a cron job, in a script, or anywhere a human isn't going to type follow-up messages.
This article covers the flag combinations that actually matter for unattended runs, the standard CI patterns, and the gotchas that catch people the first time a Claude job runs on a Friday night with nobody watching.
The -p flag, in one paragraph
claude -p "your prompt here" runs the full agent loop non-interactively. Claude reads the prompt, decides what tools to call, makes tool calls, processes the results, repeats until done, then writes the final response to stdout and exits. Exit code 0 means success; non-zero means an error. There is no chat — Claude can't ask you a clarifying question because there's nobody to ask.
Input modes
- Argument:
claude -p "summarise the README"— prompt on the command line - Stdin:
cat issue.txt | claude -p "draft a fix for this"— prompt piped in, useful when the prompt is long or contains shell metacharacters - Resume:
claude -p --resume SESSION_ID "continue"— resumes a specific previous session in print mode and adds the new prompt
Output formats
Default output is plain text — just the final response. For scripts that consume Claude's output, structured formats are essential:
--output-format text(default) — plain text response, no metadata--output-format json— single JSON object withresult,session_id, token usage, cost, and timing--output-format stream-json— newline-delimited JSON, one event per line, streamed as the agent runs. Useful for piping into observability tools or showing live progress
For most scripts, json is the right choice — you get the response and the metadata in one parseable blob:
claude -p "review src/auth.ts" --output-format json | jq -r '.result'
Restricting what Claude can do
Unattended runs need explicit constraints. Claude Code is permissive by default in interactive mode because a human is there to approve. In CI, you have to lock it down:
--allowedTools "Read,Bash"— comma-separated allowlist of tools Claude can use; everything else is blocked--disallowedTools "WebFetch,Bash(rm *)"— blocklist with optional argument matchers (the matcher syntax matches permission rules)--max-turns 10— cap the agent loop at N rounds. Claude exits with non-zero status if it hits the cap without completing--bare— skip loading hooks, skills, MCP servers, and CLAUDE.md; runs against the model with only the prompt and the built-in tools. Useful for deterministic CI runs that shouldn't depend on workspace-level configuration--permission-mode default— explicit permission mode; in CI you almost never wantbypassPermissions
A reasonable shape for a CI invocation:
claude -p "$PROMPT" \
--output-format json \
--allowedTools "Read,Edit,Bash(npm test:*),Bash(git diff:*)" \
--max-turns 15 \
--permission-mode default
Authentication in CI
Plan-based auth doesn't work in CI — there's no browser to complete /login, and you don't want CI runs eating your personal plan budget anyway. Set ANTHROPIC_API_KEY as a secret in your CI provider and export it as an env var before invoking claude. With the env var set, Claude Code uses API billing automatically.
For AWS Bedrock or Google Vertex AI, the routing is different — those use cloud IAM credentials (OIDC, instance profiles) instead of an Anthropic key. See AWS Bedrock deployment and Google Vertex AI deployment for the specifics.
The official GitHub Action
Anthropic publishes anthropics/claude-code-action for GitHub Actions integration. It handles installing Claude Code in the runner, authenticating with your ANTHROPIC_API_KEY secret, and providing helper inputs for common patterns (PR review, issue triage, scheduled maintenance).
A minimal workflow that runs Claude on every PR:
name: claude-review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: anthropics/claude-code-action@v1
with:
prompt: "Review the diff for bugs, security issues, and unclear naming. Comment inline."
allowed-tools: "Read,Bash(git diff:*),Bash(git log:*)"
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
The Action also supports the @claude mention pattern — a contributor commenting @claude please look at this on a PR or issue triggers the workflow. That pattern needs the GitHub App side configured; the Action's README walks through it.
Generic CI — Docker
For non-GitHub CI (GitLab, CircleCI, Jenkins, Buildkite), the pattern is: install Claude Code in the build environment, set the API key, run claude -p. Anthropic does not publish an official Docker image as of early 2026, so you install the CLI directly in your job environment. A typical step:
- run: |
curl -fsSL https://claude.ai/install.sh | bash
export PATH="$HOME/.local/bin:$PATH"
claude -p "$PROMPT" --output-format json --allowedTools "Read,Edit"
env:
ANTHROPIC_API_KEY: ${API_KEY_SECRET}
Common gotchas
- TTY detection. Some CI runners don't allocate a TTY by default, and Claude Code's input handling assumes interactive when stdin is a TTY. The
-pflag makes input handling deterministic; don't rely on TTY detection - Missing CLAUDE.md context. If your CI checks out the repo at a shallow depth or to an unusual directory, your
.claude/CLAUDE.mdmay not be present. Either ensure the workspace structure is intact or use--bareto opt out of context loading entirely - Secret leakage in logs. Default output may include tool calls and their arguments. If your prompt or environment contains secrets, they can end up in CI logs. Mask secrets at the CI provider level, and prefer
--output-format jsonpiped throughjqto extract only theresultfield - Exit codes. Exit code 0 means the run completed successfully — but "successfully" only means the agent loop ran to a final response, not that the response was correct. If you need stronger validation, parse the JSON output and check expected markers in
resultor run a follow-up validation step - Non-determinism. Two identical runs of the same prompt can produce different outputs — Claude is a sampling model. For tasks where reproducibility matters (e.g. version bumping where the message must match a regex), constrain the output format aggressively or post-process to canonicalise
- Cost runaway. A misconfigured run with no
--max-turnsand a prompt that leads to an infinite loop is a real risk. Cap--max-turnson every CI run; budget aggressively at first; raise as you trust the workflow
Useful patterns
- PR review — diff the branch, ask Claude to flag issues, post comments via the GitHub Action's PR-comment mode
- Scheduled maintenance — nightly or weekly cron job to clean up TODO comments, update dependencies, regenerate documentation
- Issue triage — on new issue, classify and label it; ask Claude to draft a first-pass diagnostic question to the reporter
- Release notes — on tag push, run Claude over the commits since the last tag and have it draft the changelog entry
- Headless transformation — convert content from one format to another at scale; with
--bareand a tight tool allowlist, this is reliable enough for production batch jobs
Headless Claude Code is genuinely useful for the kind of work humans hate doing — repetitive review, cleanup, formatting, classification. The investment is in setting tight tool restrictions and parsing structured output; once that's in place, the agent does the work without supervision.
