Skip to main content
Tools & MCP

Claude Code GitHub Actions: Headless PR Reviews With -p and JSON

Run Claude Code headless in CI with claude -p, get JSON findings for inline PR comments, cut false positives, and choose sync vs Message Batches APIs.

The CertGrove team · 7 min read

An automated review that hangs the pipeline, posts the same comment five times, or flags every style nit gets switched off within a week. Scenario 5 of the CCAR-F exam is built around exactly these failure modes. Here is how to run Claude Code headless in CI, get JSON findings for inline comments, and pick the right API per job.

Run Claude Code headless with `claude -p`

Plain claude "Review this PR" starts an interactive session. On a CI runner nobody is there to answer, so the job waits forever. The fix is the -p (or --print) flag: Claude Code processes the prompt, prints the result to stdout, and exits with a status code your script can check.

gh pr diff "$PR_NUMBER" | claude -p "Review this diff for bugs and security issues" \
  --allowedTools "Read,Grep,Glob"

Distractors invent a CLAUDE_HEADLESS variable, a --batch flag, or stdin from /dev/null. The documented way is -p. Add --allowedTools to pre-approve the tools the review needs, since nobody is there to click "allow".

Machine-readable findings with `--output-format json` and `--json-schema`

To post inline PR comments, your script needs a file, line and message per finding. Task statement 3.6 names two flags:

  • --output-format json wraps the response in JSON with the text in result, plus session ID, usage and cost.
  • --json-schema adds a JSON Schema. The output must conform, and it lands in the structured_output field.
claude -p "Review the changes on this branch against CLAUDE.md review criteria" \
  --allowedTools "Read,Grep,Glob,Bash(git diff *)" \
  --output-format json \
  --json-schema "$(cat .github/review-schema.json)" \
  | jq '.structured_output.findings' > findings.json

The schema can be small:

{
  "type": "object",
  "properties": {
    "findings": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "file": { "type": "string" },
          "line": { "type": "integer" },
          "severity": { "enum": ["bug", "security", "performance"] },
          "message": { "type": "string" }
        },
        "required": ["file", "line", "severity", "message"]
      }
    }
  },
  "required": ["findings"]
}

A later step posts each entry in findings.json as a review comment through the GitHub API.

The GitHub Actions version

Anthropic ships an official action, anthropics/claude-code-action@v1. In automation mode you give it a prompt, and claude_args passes any CLI flag:

name: Claude PR review
on:
  pull_request:
    types: [opened, synchronize]
jobs:
  review:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
      id-token: write
    steps:
      - uses: actions/checkout@v6
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          prompt: "Review PR #${{ github.event.pull_request.number }} using the criteria in CLAUDE.md"
          claude_args: "--max-turns 10"

The exam tests the CLI flags, not the action's inputs. Anthropic has since added a managed Code Review feature that needs no workflow file; it postdates the exam guide.

Give the CI reviewer context, and keep it independent

CLAUDE.md is the CI context. A claude -p run loads the repo's CLAUDE.md, so review criteria, testing standards and fixtures documented there reach every pipeline run. One caveat from current docs: the newer --bare flag skips CLAUDE.md, so with it you pass criteria via --append-system-prompt-file. See the CLAUDE.md hierarchy.

Re-reviews report only what is new. A fresh run has no memory of the last one and repeats everything. Pass the prior findings in and tell Claude to report only new or still-unaddressed issues. For test generation, include existing test files so Claude skips scenarios already covered.

Use an independent reviewer. The session that wrote the code keeps its reasoning and tends to agree with itself. Per task statement 4.6, a second instance without that context catches more than a self-review instruction or extended thinking.

Split large PRs into passes. One pass over 14 files gives uneven depth and contradictions. Run a pass per file for local issues, then an integration pass for cross-file data flow. A bigger context window does not fix attention dilution.

Cut false positives with explicit criteria

Noisy categories drag down trust in the accurate ones. "Be conservative" or "only report high-confidence findings" barely helps. Specific categorical rules do:

  • Report: logic bugs, security issues, a comment whose claim contradicts what the code does.
  • Skip: minor style, naming preferences, patterns that match the surrounding code.

Give each severity level a concrete code example so classification stays consistent. If one category keeps producing noise, turn it off while you fix its prompt.

Synchronous API vs Message Batches API

Match the API to the latency requirement:

WorkloadAPIWhy
Pre-merge check a developer waits onSynchronous Messages API (or claude -p)Needs a result now
Nightly test generation, weekly audit, tech-debt reportMessage Batches API50% cheaper, latency-tolerant

Batches take up to 24 hours with no latency guarantee. Each request carries a custom_id to match results to inputs and resubmit only failures. Batches also can't run multi-turn tool calling within one request.

Try it

Your CI review runs on every push to a PR. After a developer pushes fixes, the bot re-posts all eight original comments, including the three that were fixed. What should you change?

A. Add --max-turns 1 so the review stops before it repeats itself. B. Include the previous run's findings in the prompt and instruct Claude to report only new or still-unaddressed issues. C. Run the review in the same session that generated the code so it remembers earlier comments. D. Switch the re-review to the Message Batches API so results are deduplicated by custom_id.

Answer: B. A new run starts with no knowledge of earlier comments, so you have to hand it the prior findings and say what to report.

  • A limits turns, not duplication. A one-turn review can repeat all eight comments.
  • C reuses the generator's context, which makes reviews weaker, and a CI run does not carry memory between pushes anyway.
  • D confuses correlation with deduplication. custom_id matches a response to its request; it knows nothing about earlier reviews.

Key takeaways

  • -p (--print) is the documented way to run Claude Code non-interactively. Without it, CI hangs.
  • --output-format json plus --json-schema gives parseable findings in structured_output, ready for inline comments.
  • CLAUDE.md carries review criteria into CI. Prior findings carry state between re-reviews.
  • Review with an independent instance, and split big PRs into per-file passes plus an integration pass.
  • Synchronous for blocking pre-merge checks, Message Batches (50% cost, up to 24h, custom_id) for overnight jobs.

Frequently asked questions

The CertGrove teamBilingual study notes for developers preparing for Claude Certified Architect — Foundations.