
Drive Claude from Shell Scripts: claude -p, Piped Input, and --output-format json
Chris Harper
3 min read
Jun 30, 2026 · 12:13 UTC
TL;DR: claude -p "task" exits after one turn; add --output-format json and stdout becomes a machine-readable envelope with the result, token counts, and session ID — the building block for Claude in CI pipelines and scripts.
Claude Code's interactive session is powerful for exploratory work, but CI pipelines, pre-commit hooks, and shell scripts need a different pattern: input in, result out, process exits. claude -p (print mode) is that pattern.
The basics
# Ask Claude about piped content and exit
git diff HEAD~1 | claude -p "Summarize the breaking changes in this diff"
# Process a specific file
claude -p "Does this code have any security issues?" < src/auth.ts
# Combine command output with a prompt
npm test 2>&1 | claude -p "Explain these failures and suggest fixes for each"
-p runs one turn and exits with code 0 on success, non-zero on error.
--output-format json for scripting
By default, claude -p writes the model's response to stdout as plain text. Add --output-format json and you get a structured envelope:
result=$(claude -p "List the three most critical TODOs" \
--output-format json < README.md)
echo "$result" | jq -r '.result' # model's full response text
echo "$result" | jq '.usage.input_tokens' # token usage
echo "$result" | jq -r '.session_id' # for logging / correlation
The JSON envelope fields: result (response text), usage (input_tokens, output_tokens), session_id.
--output-format stream-json for long outputs
For tasks that produce large or time-sensitive output, stream-json emits one JSON object per line as the model generates:
claude -p "Audit every exported function for missing docs" \
--output-format stream-json \
2>/dev/null \
| while IFS= read -r line; do
type=$(echo "$line" | jq -r '.type // empty')
if [ "$type" = "text" ]; then
echo "$line" | jq -r '.text'
fi
done
Each line carries a type field: text, tool_use, tool_result, usage. Redirect stderr to suppress the interactive spinner.
CI pattern: pass/fail from Claude's output
# .github/workflows/security-check.yml
- name: AI security review
run: |
git diff origin/main...HEAD \
| claude -p "Review for security issues. Last line must be PASS or FAIL." \
--output-format json \
| jq -r '.result' \
| tail -1 \
| grep -q "^PASS"
Claude's exit code reflects process success, not Claude's verdict — parse the output to drive your CI gate.
Restrict tools for read-only analysis
By default, claude -p can use all tools. Limit it for safer CI runs:
claude -p "Review this file for logic errors" \
--allowedTools "Read,Grep,Glob" \
--output-format json \
< src/service.ts
--allowedTools is a comma-separated list. A read-only set (Read,Grep,Glob) gives Claude full text analysis with no filesystem writes, no shell execution, no network.
Sources: