Lesson 3.6
Headless Mode, SDK and CI/CD Integration
Running Claude Code without a terminal user: the print mode flags, output formats, permission strategy for unattended runs, and GitHub Actions integration.
Headless mode runs Claude Code as a non-interactive command: you pass a prompt, it works, it prints a result and exits. This is the basis for scripting, scheduled jobs, pull-request automation and the Agent SDK, which wraps the same engine. The architectural challenge is that there is no human to answer permission prompts, so the permission strategy, budget limits and output parsing must be decided in advance. The exam tests the flags and the trade-offs.
Print mode basics
claude -p "Summarise the open TODO comments in src/ as a markdown table"
-p (or --print) makes the run non-interactive. Useful companions:
| Flag | Purpose |
|---|---|
--output-format text|json|stream-json | Plain text, a single JSON result with cost and session ID, or newline-delimited JSON events as they happen |
--input-format stream-json | Accept a stream of messages on stdin for multi-turn automation |
--allowedTools "Read,Bash(npm test:*)" | Pre-approve tools for this run |
--disallowedTools "Write,Edit" | Remove tools for this run |
--permission-mode acceptEdits|plan|dontAsk|bypassPermissions | Set the posture |
--max-turns N | Cap agentic iterations |
--model, --fallback-model | Choose the model and a fallback on overload |
--system-prompt, --append-system-prompt | Replace or extend the default system prompt |
--resume <id>, --continue | Continue an earlier headless session |
--mcp-config, --strict-mcp-config | Exact MCP servers for the run |
--verbose | Full turn-by-turn log for debugging |
Piping works in both directions: cat error.log | claude -p "Explain the root cause" feeds stdin as context, and --output-format json gives a parseable result with fields such as result, session_id, total_cost_usd, num_turns and is_error. stream-json emits the init, assistant, user and result events the Agent SDK exposes as typed messages, which is what to use when a wrapper needs progress updates.
Permissions without a human
Three strategies exist, in order of preference:
- Allow list plus
dontAsk. Enumerate exactly what the job needs (Read,Grep,Bash(npm test:*)) and deny everything else by default. The run fails fast if Claude attempts anything unexpected. - Programmatic approval.
--permission-prompt-toolnames an MCP tool that is called whenever a permission prompt would appear; your tool implements the policy (or asks a human through a chat system) and returns allow or deny. APermissionRequesthook can do the same from settings. - Bypass in a sandbox.
--dangerously-skip-permissionsinside an ephemeral container with no production credentials, scoped tokens and no network beyond what the job needs. The container is the guardrail.
Never run bypass mode on a developer machine or a shared runner with live credentials. The flag's name is the policy.
Budgets and determinism
Unattended runs need hard limits: --max-turns for iterations, a budget cap where the version supports it, a job-level timeout from the CI system, and --strict-mcp-config so the server set is exactly what the pipeline declares. Pin the model explicitly rather than inheriting a developer's default, and version the prompt in the repository so changes are reviewable.
GitHub Actions
The official anthropics/claude-code-action runs Claude Code inside a workflow. The simplest setup comes from /install-github-app in a session, which installs the GitHub App and generates a workflow. A trimmed example:
name: Claude
on:
issue_comment: { types: [created] }
pull_request_review_comment: { types: [created] }
jobs:
claude:
if: contains(github.event.comment.body, '@claude')
runs-on: ubuntu-latest
permissions: { contents: write, pull-requests: write, issues: write, id-token: write }
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 1 }
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: "--max-turns 15 --allowedTools Read,Edit,Bash(npm test:*)"
The action responds to @claude mentions in issues and pull requests, or runs a fixed prompt on a schedule or on push for tasks such as automated review. claude_args passes through the same flags as the CLI. Credentials can also come from cloud providers (Bedrock, Vertex AI or Foundry) through OIDC rather than a stored API key. GitLab CI and other systems run the CLI directly in a container with the same flags.
The Agent SDK relationship
The Agent SDK is the programmatic form of headless mode. query() in Python or TypeScript launches the same engine, and the SDK's typed messages correspond to --output-format stream-json. Choose the SDK when the orchestration logic lives in your application (custom tools, hooks as functions, dynamic prompts); choose the CLI when a shell step in a pipeline is enough. Note that the SDK does not load project settings or CLAUDE.md unless settingSources asks for them, whereas the CLI does.
Exam focus
claude -pruns non-interactively;--output-format jsonreturns a parseable result withsession_idand cost, andstream-jsonemits events as they occur.- Unattended permission strategy, in order of preference: allow list with
dontAsk, programmatic approval via--permission-prompt-toolor aPermissionRequesthook, then bypass inside an ephemeral sandbox only. --max-turns, job timeouts, a pinned model and--strict-mcp-configmake runs bounded and reproducible.anthropics/claude-code-actionis the official GitHub Actions integration;/install-github-appbootstraps it;claude_argscarries CLI flags.- Credentials in CI come from secrets or cloud OIDC, never from a committed file.
- The Agent SDK wraps the same engine as headless mode but loads no filesystem settings by default.
Check yourself
5 questions
1.A CI job needs Claude Code's answer in a machine-readable form and must record the session identifier for a later step. Which command is correct?
2.Which flag runs Claude Code non-interactively so it processes one prompt and exits?
3.A pipeline runs claude -p inside the checkout of an untrusted pull request. Hooks defined in the PR's .claude/settings.json and servers in its .mcp.json execute during the run. What is the recommended mitigation?
4.A team wants a locked-down headless run in which Read, Grep and Glob are pre-approved and anything else that would prompt is denied rather than hanging. Which flags achieve this?
5.A script needs Claude Code's headless output to conform to a specific JSON schema so it can be parsed without heuristics. What should it use?
