AI Automation

Running Claude Code headless (claude -p): agents in scripts, cron and CI

TL;DR

claude -p "prompt" runs one turn and exits, printing to stdout. Add --output-format json to parse it, --allowedTools to limit permissions and --max-turns to stop runaway loops. Great for scheduled reports, automated reviews and changelogs.

Claude Code isn’t only a chat tool in your terminal. Add the -p flag and it becomes an ordinary CLI command: take a prompt, do the work, print the result to stdout and exit — ready for scripts, cron jobs, Git hooks and CI pipelines. This guide goes from the basic command to a safe configuration for unattended runs.

The basic command

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

-p (or --print) runs non-interactively. Claude Code exits 0 on success and non-zero on failure, so your scripts can branch on it. Failures during the run (e.g. missing authentication) are printed to stdout as the result.

Piping data in and out

# Explain a failed build log
cat build-error.txt | claude -p "Concisely explain the root cause of this build error" > output.txt

# Write a changelog from today's commits
git log --since=midnight --oneline | claude -p "Write a short changelog grouped by feature/fix"

Piped stdin is capped at 10 MB; for larger inputs write a file and reference it in the prompt.

--bare mode: use it for scripts and CI

By default claude -p auto-loads hooks, skills, subagents, plugins, MCP servers, auto memory and CLAUDE.md like an interactive session — even in folders you’ve never trusted, and without any trust dialog. Add --bare to skip all of that discovery:

claude --bare -p "Summarize README.md" --allowedTools "Read"
  • Faster startup and identical results on every machine — a teammate’s hooks in ~/.claude or MCP servers in the repo’s .mcp.json won’t run.
  • No OAuth or keychain: set ANTHROPIC_API_KEY (or Bedrock / Google Cloud / Foundry credentials).
  • Pass what you need explicitly: --append-system-prompt, --settings, --mcp-config, --agents, --plugin-dir.

The official docs recommend --bare for all scripted and SDK calls and say it will become the default for -p in a future release.

Structured results

--output-formatResult
text (default)Plain text
jsonJSON with result, session_id, metadata, total_cost_usd
stream-jsonOne JSON event per line, for real-time streaming
# Just the text result
claude -p "Summarize this project" --output-format json | jq -r '.result'

# Force output into a JSON Schema
claude -p "List the main function names in auth.py" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \
  | jq '.structured_output'

Schema output lands in structured_output. If Claude declines or can’t produce valid output, the field is absent — your script must check for it. total_cost_usd lets you track each run’s cost (a client-side estimate).

Permissions — the most important part of unattended runs

Nobody is there to click “Allow”, so say up front what Claude may do:

# Allow only the git commands needed
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 *)"
  • --allowedTools uses permission-rule syntax. A * after a space allows any command with that prefix — Bash(git diff *) differs from Bash(git diff*) (the latter also matches git diff-index).
  • Calls that need approval and aren’t allowed are denied (recent versions start -p runs in Manual mode).
  • --permission-mode sets a baseline: dontAsk (deny anything that would prompt — good for locked-down CI), acceptEdits (file edits and basic file commands), auto (a classifier model approves instead of you).
  • --permission-prompts none for unattended jobs: nothing waits for an answer, requests needing approval are denied, and Claude is told not to retry.

Customising the system prompt

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

--append-system-prompt adds instructions while keeping Claude Code’s default behaviour; --system-prompt replaces it entirely.

Multi-turn conversations

# First turn
claude -p "Review this codebase for performance issues"
# Continue the most recent conversation
claude -p "Now focus on the database queries" --continue

# Or keep the session_id to return to a specific session
sid=$(claude -p "Start a review" --output-format json | jq -r '.session_id')
claude -p "Continue that review" --resume "$sid"

Real-world uses

TaskTriggerSuggested setup
Progress report from commits + ticketsDaily cron at 5 pm--bare, Read + Bash(git log) only
PR review against the team checklistGitHub Actions on PR openPipe gh pr diff, JSON output
Classify last night’s error logsCron at 7 am--json-schema feeding a dashboard
Typo check on a diffnpm scriptPipe git diff main — no Bash permission needed
Release notesOn tag creationText output into a Markdown file

Staying safe when automating

  • Always declare a minimal --allowedTools; avoid skipping permissions on machines with real data.
  • Use --bare in CI so an unfamiliar repo can’t run its own hooks or MCP servers.
  • Run in a dedicated container/runner with a dedicated automation API key and spend limit; cap each run with --max-budget-usd.
  • Check the exit code and the presence of structured_output before processing further.
  • Need custom tools, permission callbacks or finer message handling → move to the Claude Agent SDK (Python/TypeScript).

How to apply it

  • Developers: Add a lint:claude script that pipes your diff into Claude to catch typos and naming issues before opening a PR — no Bash permission required.
  • Tech Leads: Standardise one CI wrapper script: always --bare, always an allowlist, always --output-format json, and log total_cost_usd.
  • PMs: Set up a 5 pm cron that turns commits and tickets into a daily report posted to the team channel — instead of asking “what did you do today” in stand-up.

Sources & references

  1. Run Claude Code programmatically — Claude Code Docs