CI/CD Integration

Automate Claude Code in pipelines: run headless with -p, emit machine-parseable JSON, and separate generation from review.

Lesson 18 of 3060% of the guide
Prefer to learn by doing?

Claude Code defaults to an interactive, conversational interface — and a CI pipeline has no keyboard. Turning it into a reliable automation engine is mostly about running it non-interactively and getting machine-readable results back. This lesson is one of the most heavily tested in Domain 3.

The -p (print) flag

The single most important element is the -p (print) flag. Without it, a CI job hangs forever waiting for input. With it, Claude runs the prompt once, prints the result, and exits.

# Non-interactive, single-shot execution
claude -p "Analyse this pull request for security issues"
Fixing a CI job that hangs
Don't

Try to force headless mode with an environment variable or by redirecting stdin. Claude still waits for interactive input and the job hangs forever.

CLAUDE_HEADLESS=true claude "Review this PR"  # still hangs
Do

Pass the `-p` (print) flag. Claude runs the prompt once, prints the result, and exits.

claude -p "Review this PR for security issues"
The fix for hanging is -p

A CI job that hangs is fixed by the -p flag, not by an environment variable like CLAUDE_HEADLESS=true or by redirecting stdin. This is a frequently tested detail.

Machine-parseable output

For a pipeline to act on results, ask for structured output. --output-format json wraps the response in an envelope with the result text, session id, and cost metadata. --json-schema validates the final output against a schema, landing the conforming data in the envelope's structured_output field where jq can extract it.

# .github/workflows/review.yml (excerpt)
jobs:
  claude-review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Security review
        run: |
          claude -p \
            --output-format json \
            --json-schema '{"type":"object","properties":{"findings":{"type":"array"}}}' \
            "Review this PR for security issues" \
            | jq '.structured_output'

Separate generation from review

The same session that wrote code is weaker at reviewing it, because it carries the reasoning that justified those choices and won't question them. Run independent invocations: one session to generate, a separate one to review. To avoid repeating the same comments on every push, feed prior findings into the review and ask only for new or unaddressed issues — this preserves the signal-to-noise ratio.

# Session A: generate
claude -p "Implement the authentication middleware"

# Session B: independent review
claude -p "Review the authentication middleware for security issues"

CLAUDE.md and key CLI flags

Claude Code reads project CLAUDE.md in CI exactly as it does interactively, so document testing standards, fixtures, and review severity there — without it, CI-generated tests tend toward low-value boilerplate. Useful headless flags include --append-system-prompt (adds to the default, keeping tool guidance and safety, versus --system-prompt which replaces everything), --max-turns to cap agentic turns, and --permission-mode / --allowedTools to control tool access non-interactively.

FlagPurpose
-pNon-interactive single-shot run (headless)
--output-format jsonWrap result in a parseable envelope
--json-schemaValidate output against a schema
--append-system-promptAdd to default prompt (keeps safety/tools)
--max-turnsCap the number of agentic turns

Batch API vs real-time

The Message Batches API is about 50% cheaper but can take up to 24 hours with no latency guarantee. So use the real-time API for blocking pre-merge checks where developers wait on the result, and reserve the Batch API for non-time-sensitive work like overnight reports or weekly audits. Using Batch for a blocking gate is a classic wrong answer.

Choosing the API for a pre-merge gate
Don't

Use the Batch API for a blocking pre-merge check to save ~50%. Developers may wait up to 24 hours for a result — the gate stalls every PR.

Do

Use the real-time API for blocking checks where developers wait; save the Batch API for non-urgent overnight reports or weekly audits.

Headless essentials

Run with -p, emit --output-format json (optionally --json-schema), review in a session separate from generation, and choose real-time vs Batch API by whether developers are waiting.

Append, don't replace, the system prompt

--append-system-prompt keeps Claude's default tool guidance and safety instructions; --system-prompt drops all of it. Prefer append unless you truly need a clean slate.

How the exam will try to trick you

The distractors below look right under time pressure — learn the tell.

  1. The trap

    Fix a hanging CI job by setting CLAUDE_HEADLESS=true, adding --batch, or redirecting stdin from /dev/null.

    Correct answer

    Pass the -p (--print) flag so Claude runs the prompt once, prints to stdout, and exits.

    Why: None of those toggles exist; without -p Claude still waits for interactive input that never arrives.

  2. The trap

    Generate the code and review it in the same Claude Code session to keep context.

    Correct answer

    Run generation and review as separate, independent invocations.

    Why: The generating session carries the reasoning that justified its choices, so it won't challenge them; a fresh reviewer judges the code on its merits.

  3. The trap

    Use the Message Batches API for a blocking pre-merge check to save ~50%.

    Correct answer

    Use the real-time API for blocking pre-merge checks; reserve Batch for overnight or weekly analysis.

    Why: Batch offers no latency SLA (up to 24 hours), so a blocking gate would stall every PR.

  4. The trap

    Run each push's review from scratch with no prior context.

    Correct answer

    Feed previous findings into the review and ask only for new or unaddressed issues.

    Why: Reviewing from scratch reproduces the same comments every run, eroding trust and signal-to-noise.

Key takeaways

  • The -p flag makes Claude Code run non-interactively; without it CI hangs.
  • --output-format json plus --json-schema yield machine-parseable, validated results.
  • Review in a session independent from the one that generated the code.
  • Feed prior findings back in to avoid duplicate review comments.
  • CLAUDE.md is read in CI, so document standards and fixtures there.
  • Use real-time API for blocking checks; Batch API for non-urgent reports.

Frequently asked questions

How do I run Claude Code in a CI/CD pipeline?+

Invoke it with the -p (print) flag so it runs the prompt once and exits instead of waiting for keyboard input. Add --output-format json and optionally --json-schema so the pipeline can parse and validate the result programmatically.

Why should code review use a different Claude session than generation?+

The session that generated code carries the reasoning that justified its choices, so it is less likely to challenge them. An independent review session evaluates the changes without that bias and catches issues the original session would rationalize.

When should I use the Batch API instead of the real-time API?+

Use the Batch API for non-time-sensitive work like overnight reports or weekly audits, where its roughly 50% cost saving is worth up to 24 hours of latency. Use the real-time API for blocking pre-merge checks where developers are waiting on the result.

Practice makes pass

Ready to test what you just learned?

Reading gets you familiar — answering questions gets you certified. Jump into free practice or sit a full timed mock exam, scored 100–1000 just like the real thing.

No sign-up required · Explanation for every answer · Works offline