CCAF logo

Domain 3 · Task 3.6

CI/CD Integration

Integrate Claude Code into CI/CD pipelines.

Running Claude Code in CI means making it headless and machine-parseable, and structuring reviews so they are trustworthy. The three load-bearing facts: -p for non-interactive mode (a bare claude "..." hangs), --output-format json with --json-schema for structured output, and the insight that the session which generated code is worse at reviewing it than an independent instance.

Key concept

CI must be headless (-p) and machine-parseable (--json-schema). Review needs an independent instance plus prior-findings context to report only new or unaddressed issues. CLAUDE_HEADLESS and --batch do not exist.

What you need to know

Headless mode: -p and structured output

A bare claude "..." call hangs waiting for interactive input, which stalls the pipeline. The fix is -p / --print: process the prompt, print to stdout, and exit. For output CI can act on, combine --output-format json with --json-schema so results are schema-validated and machine-parseable — ready to post as inline PR comments. Note the anti-patterns: CLAUDE_HEADLESS=true and a --batch flag do not exist, and stdin redirection does not fix the hang. The one correct fix for a hanging CI call is adding -p.

bash
# WRONG — hangs waiting for interactive input
claude "review the changed files"

# RIGHT — headless + machine-parseable
git diff main --name-only \
  | claude -p "review these changed files for security issues" \
      --output-format json \
      --json-schema ./review-schema.json \
  | jq '.structured_output'

Independent review and avoiding duplicate comments

Session-context isolation: the session that generated code is less effective at reviewing its own changes — it carries its own reasoning context and will not challenge itself. Use a separate, independent instance for review. When re-running after new commits, include the prior findings and instruct Claude to report only new or still-unaddressed issues, so the pipeline does not spam duplicate comments across pushes. Provide the existing test files too, so test generation does not re-cover scenarios that are already tested.

yaml
# .github snippet — headless review as a build step
- name: Claude review
  run: |
    gh pr diff "${{ github.event.pull_request.number }}" \
      | claude -p "You are an independent reviewer. Report ONLY new or unaddressed issues." \
          --output-format json --json-schema ./review-schema.json \
      > findings.json

CLAUDE.md as CI project context

CI-invoked Claude Code still reads CLAUDE.md, which is how you supply project context to a headless run: testing standards, fixtures, and review criteria. Documenting testing standards and fixtures there raises test quality and cuts low-value output — the CI instance writes tests that match your conventions and skips scenarios already covered. In short, the pipeline flags (-p, --json-schema) make Claude Code a deterministic build step, and CLAUDE.md makes that build step aware of the project it is running against.

Exam traps

The trapThe reality
A claude "..." call hanging in CI is fixed by setting CLAUDE_HEADLESS=true or adding a --batch flag.Neither exists. The bare call hangs on interactive input; add -p / --print to run non-interactively.
Redirecting stdin into the command resolves the CI hang.The problem is interactive-mode invocation, not missing input. -p is the correct fix.
The session that wrote the code is the most efficient one to review it, since it already has all the context.That session is worse at reviewing its own changes — it will not challenge itself. Use an independent instance.
Re-running review after each push should just re-report everything it finds.Include prior findings and report only new or unaddressed issues, or the PR fills with duplicate comments.

Practice scenario

Real questions from the bank that test this topic — the correct answer is highlighted.

Your company runs Claude Code in CI for automated code review. Constraints:

  • Must be non-interactive (no TTY prompts)
  • Output must be parseable as JSON for downstream processing
  • The agent should only be allowed to use Read , Glob , and Grep
  • The CI worker has a 5-minute timeout

Which invocation is correct?

Aclaude chat --ci --readonly --timeout 300 --format json
Bclaude -p "review the diff" --output-format stream-json --allowed-tools Read,Glob,GrepCorrect
Cclaude --headless --tools=read,glob,grep --format json --max-time 300
Dclaude run --batch --output json --tools "Read,Glob,Grep"

Why: Headless invocation: claude -p "prompt" (the -p is "print" / non-interactive). --output- format supports text , json , stream-json . --allowed-tools takes a comma-separated list. The other invocation patterns (A, C, D) mix invented flag names with plausible-looking syntax.

A startup deploys Claude Code in CI to review every PR. They have these constraints:

The CI job has a hard 6-minute wall-clock budget

The agent must only use Read , Glob , Grep , and Bash(gh:*) — never anything that could mutate the repo

Output must be machine-parseable JSON so it can be posted as a structured PR comment

The CI runner has no TTY

A junior engineer's first attempt is:

claude --interactive --tools "Read,Glob,Grep,Bash(gh:*)" --timeout 360 --format json

The senior reviewer rejects it. What are the specific problems?

A--interactive is wrong (should be -p for headless print mode); --tools is the wrong flag name (should be --allowed-tools ); --format is wrong (should be --output-format ); --timeout isn't a real Claude Code flag — wall-clock budgeting must be enforced by the CI runner itself (e.g., GitHub Actions timeout-minutes )Correct
BThe tool list Bash(gh:*) is invalid syntax — Bash patterns aren't supported in CLI flags, only in settings.json
C--format json is fine but the agent needs an additional --ci-mode flag for CI environments
DThe whole approach is wrong; CI integration must use the Anthropic API directly, not the claude CLI

Why: This question tests headless mode flags concretely. The correct invocation: claude -p "review the diff" --output-format json --allowed-tools "Read Glob Grep Bash(gh:*)" with wall-clock budgeting handled by the CI runner (e.g., GitHub Actions timeout-minutes: 6 ). The junior's mistakes: - --interactive is wrong; -p (print) is headless mode - --tools doesn't exist as a flag; --allowed-tools is correct - --format is wrong; --output-format is correct - --timeout isn't a Claude Code flag — there's no built-in wall-clock timeout; CI runners handle that B is wrong because Bash(gh:*) patterns are valid in --allowed-tools . C invents --ci- mode . D invents a constraint that CI must use the API directly.

Build exercise

Wire a headless Claude Code review into CI

~45 min
  1. 1
    Reproduce the hang: run a bare claude "..." in a non-interactive shell and watch it wait forever.

    Why: Seeing the hang cements why headless mode is mandatory in CI.

  2. 2
    Re-run with -p and confirm it processes the prompt, prints to stdout, and exits.

    You should see: The command completes without waiting for input.

  3. 3
    Add --output-format json and --json-schema pointing at a review schema, and parse the result with jq.

    You should see: Machine-parseable, schema-conforming findings you can post as inline PR comments.

  4. 4
    Pipe the PR diff into a fresh, independent Claude Code instance for review rather than the generating session.

    Why: An independent instance reviews more effectively than the one that wrote the code.

  5. 5
    On a second push, pass the prior findings and instruct it to report only new or unaddressed issues; add a CLAUDE.md with your testing standards.

    You should see: No duplicate comments across pushes, and generated tests follow the documented standards.

Sources

Drill Claude Code Configuration & Workflows

Practice only this domain’s questions, untimed, with instant explanations.