Script it only after it works interactively
You will write a bounded non-interactive call, choose the right output format and keep CI runs free of interactive prompts. Outputs are illustrations.
Headless and Agent SDK sources checked September 30, 2026 describe claude -p for scripts and CI, with --bare for reproducible runs and JSON output formats for machines. A scripted call has no one to ask, so permission planning is the design.
Prove the prompt interactively first
A prompt that works once in a session can fail silently in CI: different working directory, missing context, no human to answer. Run it interactively in the practice repo, then convert it with explicit flags.
Design a non-interactive typo check for the course content files.
Input: the diff or file list supplied by the caller, not broad repo access.
Output: filename:line findings only; empty output means clean.
No edits, no network, no hooks or MCP in the run.
Bare mode for reproducibility
--bare skips auto-discovery of hooks, skills, plugins, MCP servers, auto memory and CLAUDE.md, so the run does not pick up whatever a teammate configured. Authentication then comes from ANTHROPIC_API_KEY or an apiKeyHelper in explicit settings, not OAuth.
git diff main | claude --bare -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." --allowedTools "Read"
Piping the diff means Claude never needs shell access to get the data. Piped stdin is capped at 10MB; larger inputs belong in files referenced by path.
Formats for humans and machines
--output-format json returns metadata including total_cost_usd, so scripted callers can track spend per run. stream-json emits newline-delimited events. Add --json-schema when downstream code expects a shape, and treat the format keyword as annotation, not enforcement.
claude --bare -p "List the chapter titles changed in this diff" --output-format json | jq -r '.result'
Always parse: a script that greps prose output breaks the first time the phrasing changes.
No prompter means no prompts
In -p runs there is no one to approve tool calls: they follow configured rules, and unapproved needs fail or wait without a human. --allowedTools pre-approves a narrow set; keep it minimal. A CI job that needs Write for a lint task is a design smell.
Background Bash tasks started during a -p run are terminated shortly after the final result; background subagents and workflows are waited on, capped by default. A stuck agent cannot hold your CI open forever, but it can waste minutes, so scope the task tightly.
Graded practice
Easy: conversion
Turn an interactive prompt into a -p call. Success: same output, explicit flags.
Intermediate: format
Choose text, json or stream-json for three consumers. Success: machine consumers get structured output.
Challenging: failure mode
The prompt needs a tool that is not allowed. Success: clear failure, not silent wrong answer.
Troubleshooting
Works locally, fails in CI: check bare mode, auth and working directory.
Empty output treated as success: distinguish clean from crashed via exit code.
Over 10MB stdin: reference a file path instead.
Hang on background task: inspect the wait cap and task necessity.
Cost surprise: read total_cost_usd in JSON output per run.