CertSafari
    Anthropic Claude Certified Architect – Foundations (CCAR-F)· Lessons

    Domain 3 · Lesson 18/30

    Headless Claude Code in CI: -p, Tool Permissions and JSON Output

    Integrate Claude Code into CI/CD pipelines

    12 min read
    3.33% of exam
    3 sources
    Published 4 Oct 2026
    Docs as of 30 Sep 2026

    What you will be able to do

    • Explain what the -p (--print) flag does to a Claude Code run and why a CI job needs it
    • Pre-approve tools with --allowedTools or choose a permission mode so an unattended run never waits on a prompt
    • Use --output-format json and --json-schema, and know which JSON field holds the result in each case
    • Wire Claude Code into a GitHub Actions workflow that posts inline PR comments

    Key concept

    Headless (non-interactive) run — With claude -p, Claude Code takes one prompt, plus anything piped in, runs the task and exits without opening an interactive session. Everything a pipeline needs to control has to go in at launch: the prompt, which tools are pre-approved, the output format and any extra context. Nobody is there to answer questions mid-run.

    Running claude with no arguments opens an interactive session. Even claude "query" opens one, with the query as its first message. Both wait for a human to type the next message, so a CI runner would just sit there. The -p (or --print) flag changes this. In the CLI reference, claude -p "query" is the form that queries and then exits. Because the process ends when the task ends, you can put it in a script, a package.json command or a pipeline step.

    Headless mode also reads standard input. You can pipe in a build log, a diff or a file and send the output straight to a file or to another program:

    Piping a build log into a headless run and writing the answer to a filebash
    cat build-error.txt | claude -p 'concisely explain the root cause of this build error' > output.txt

    Note what -p does *not* do. It stops Claude Code from waiting for your next chat message, but Claude can still try a tool call that needs approval. The headless docs describe a run that is waiting for an answer to a permission prompt. On a CI runner, nobody will answer it. The next section covers how to close that gap.

    Checkpoint 1 of 8· Check yourself

    A pipeline step runs claude "explain the failing test" and the job hangs until it times out. What is the most direct fix?

    A headless run is only useful to a pipeline if its output can be parsed. Plain text is the default. Add --output-format json and the run prints structured JSON with the result, session ID and metadata, which a later step can read with jq. To force the answer into a fixed shape, such as a list of findings to post as PR comments, add --json-schema with a JSON Schema. In the docs' example, the schema-conforming output is read from .structured_output, while the plain-text answer without a schema is in .result. The next sections show how this output feeds a PR-comment workflow.

    Headless run constrained by a JSON Schemabash
    claude -p "Extract the main function names from auth.py" \
      --output-format json \
      --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}'

    Sources12

    2.Removing permission prompts from unattended runs

    A headless run that reaches a permission prompt just stops there. The docs say that if you send SIGTERM in that state, the prompt is left unanswered. In CI, this means the job hangs until the runner kills it. You prevent this by deciding before launch what Claude may do. There are two levers.

    The first is --allowedTools, which the headless docs list as the flag for auto-approving tools. Rules can be scoped to particular commands. This example lets Claude inspect the repo and commit, and nothing else in the shell:

    Checkpoint 2 of 8· Fill the gap

    Which flag pre-approves exactly these git commands for a headless run?

    claude -p "Look at my staged changes and create an appropriate commit" \
       ?  "Bash(git diff *),Bash(git log *),Bash(git status *),Bash(git commit *)"

    The second lever is --permission-mode. The docs describe several modes for non-interactive runs:

    Permission modes the headless docs describe for non-interactive runs
    ModeWhat it does in a -p run
    dontAskDenies every call that would otherwise prompt. Reads in working directories, the read-only command set and --allowedTools matches still run
    acceptEditsWrites files without prompting and auto-approves common filesystem commands such as mkdir, touch, mv and cp
    autoA classifier reviews most actions instead of you
    A headless lint-fix run in acceptEdits modebash
    claude -p "Apply the lint fixes" --permission-mode acceptEdits

    For a locked-down job, combine dontAsk with a narrow --allowedTools list. Anything outside the list is denied straight away instead of waiting on a prompt. Note that acceptEdits covers only file edits and a few filesystem commands. For other shell commands and network requests, the docs say you still need an --allowedTools entry or a permissions.allow rule.

    Checkpoint 3 of 8· Exam question

    A platform team wraps `claude` inside a GitHub Actions job that fires on every pull request to summarize the diff. The first run hangs until the job times out, with no output ever written to the log. The team confirms the API key secret is valid and the prompt text is correct. What is the most likely cause of the hang?

    Checkpoint 4 of 8· Check yourself

    You want a CI review job in which any tool call you have not pre-approved is refused, not left waiting. Which setup matches?

    Sources2

    3.Machine-readable results: --output-format json and --json-schema

    By default a headless run prints plain text, which works for a human reading a log. A pipeline that posts comments, gates a merge or files tickets needs data it can parse. --output-format offers three formats:

    Checkpoint 5 of 8· Match them up

    Match each --output-format value to what it produces

    Tap a term, then the definition that fits it.

    --output-format json alone gives you an envelope: the free-text answer is in .result, along with the session ID and metadata. If you also need the answer to have a fixed shape, such as a list of findings each with a file, line and message, add --json-schema with a JSON Schema. The output that matches the schema is in a separate .structured_output field:

    Enforcing a schema and extracting the structured part with jqbash
    claude -p "Extract function names from auth.py" \
      --output-format json \
      --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \
      | jq '.structured_output'

    This is how you turn a review into inline PR comments. Define a schema for a finding, run the review with --output-format json --json-schema ..., and have the next pipeline step read .structured_output and post one comment per item. The schema keeps the posting script from having to parse prose. The docs show the schema and jq parts. The posting step is your own pipeline code.

    Checkpoint 6 of 8· Fill the gap

    No --json-schema is passed here. Which jq path extracts Claude's answer?

    claude -p "Summarize this project" --output-format json | jq -r ' ? '

    Sources2

    4.Running it from GitHub Actions

    The Claude Code GitHub Action wraps the same CLI. It has two behaviours. Without a prompt input, Claude waits for an @claude mention. With a prompt input it runs in automation mode, where Claude runs without waiting for a mention. Automation mode is what you want for a review on every PR event. CLI flags go in claude_args, the Action input described as CLI arguments passed to Claude Code. This is where --allowedTools, --max-turns and --model go.

    Here is the docs' code-review step. The --comment flag tells the review to post on the PR, and claude_args names the inline-comment tool:

    Code Review workflow step: plugin review that posts inline commentsyaml
          - uses: anthropics/claude-code-action@v1
            with:
              anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
              plugin_marketplaces: "https://github.com/anthropics/claude-code.git"
              plugins: "code-review@claude-code-plugins"
              prompt: "/code-review:code-review --comment ${{ github.repository }}/pull/${{ github.event.pull_request.number }}"
              claude_args: '--allowedTools "mcp__github_inline_comment__create_inline_comment"'

    The claude_args line is not optional. The skill's own frontmatter names the same tool, but the Action starts the server that posts inline comments only if --allowedTools in claude_args names it. The docs also recommend limiting unattended runs: set --max-turns in claude_args, add workflow-level timeouts, and use GitHub's concurrency controls to limit parallel runs.

    Checkpoint 7 of 8· Exam question

    An engineering lead wants CI-invoked Claude Code reviews to consistently flag missing test coverage using the team's specific definition of a "valuable test" (asserts behavior, not implementation details) and to know which fixtures already exist in the test helpers directory. Where should this project-specific guidance be encoded so every CI run picks it up automatically without being repeated in each workflow prompt?

    Checkpoint 8 of 8· Check yourself

    A PR-review workflow uses the code-review plugin with --comment, but no inline comments ever appear on the PR. The skill's frontmatter lists the inline-comment tool. What is most likely missing?

    Sources3

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 1.Adding -p is enough to make a CI run safe from hanging.Why is that wrong?

      -p only stops Claude Code from waiting for your next message. A tool call that needs approval can still stall the run unless --allowedTools, a permissions.allow rule or a mode such as dontAsk covers it.

      Covered in Removing permission prompts from unattended runs

    2. 2.With --json-schema, the schema-shaped findings replace the text in the result field.Why is that wrong?

      The schema-conforming data goes in a separate structured_output field. Read it with jq '.structured_output', not '.result'.

      Covered in Machine-readable results: --output-format json and --json-schema

    3. 3.If a skill's allowed-tools frontmatter names the inline-comment tool, the Action can post inline comments.Why is that wrong?

      The Action starts the inline-comment MCP server only when --allowedTools in claude_args names that tool.

      Covered in Running it from GitHub Actions

    Sources

    Every claim above is drawn from one of these pages, quoted as it was written on the date shown.

    1. 2.
      “json: structured JSON with result, session ID, and metadata”
      ↩︎ The -p flag: one prompt in, one answer out
      “--allowedTools for auto-approving tools”
      ↩︎ Removing permission prompts from unattended runs
      “Apart from the read-only command set, other shell commands and network requests still need an --allowedTools entry or a permissions.allow rule”
      ↩︎ Removing permission prompts from unattended runs
      “json: structured JSON with result, session ID, and metadata”
      ↩︎ Machine-readable results: --output-format json and --json-schema
      “if you send SIGTERM to the process, Claude Code leaves the prompt unanswered”
      ↩︎ Exam trap 1
      “| jq '.structured_output'”
      ↩︎ Exam trap 2
      “dontAsk: Claude Code denies every call that would otherwise prompt, which is useful for locked-down CI runs.”
      ↩︎ Checkpoint
      “| jq '.structured_output'”
      ↩︎ Prediction
    2. 3.
      “Automation mode: when the workflow provides a prompt input, Claude runs without waiting for a mention”
      ↩︎ Running it from GitHub Actions
      “CLI arguments passed to Claude Code”
      ↩︎ Running it from GitHub Actions
      “Set --max-turns in claude_args to limit iterations”
      ↩︎ Running it from GitHub Actions
      “--comment: Claude posts its review on the pull request, as an inline comment on each issue it finds”
      ↩︎ Running it from GitHub Actions
      “the Claude Code GitHub Action starts the MCP server that posts inline comments only when --allowedTools in claude_args names it”
      ↩︎ Exam trap 3
      “the Claude Code GitHub Action starts the MCP server that posts inline comments only when --allowedTools in claude_args names it”
      ↩︎ Checkpoint

    Continue to page 2 of 2

    Context for CI Reviews and Test Generation: CLAUDE.md, Independent Reviewers, Re-runs

    Spotted a mistake, or was something unclear? Tell us.