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.
1.The -p flag: one prompt in, one answer out
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:
cat build-error.txt | claude -p 'concisely explain the root cause of this build error' > output.txtNote 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?
Without -p, claude "query" opens an interactive session with that query as its first message, so it waits for more input. With -p it queries and exits.
“Query via SDK, then exit”Source: code.claude.com
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.
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"]}'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 *)"--allowedTools takes a list of tool rules that are approved without a prompt. Each Bash(...) entry here allows one family of git commands.
Source: code.claude.comThe second lever is --permission-mode. The docs describe several modes for non-interactive runs:
| Mode | What it does in a -p run |
|---|---|
| dontAsk | Denies every call that would otherwise prompt. Reads in working directories, the read-only command set and --allowedTools matches still run |
| acceptEdits | Writes files without prompting and auto-approves common filesystem commands such as mkdir, touch, mv and cp |
| auto | A classifier reviews most actions instead of you |
claude -p "Apply the lint fixes" --permission-mode acceptEditsFor 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?
Correct answer: A — The job invoked claude without the -p flag, so Claude Code started in interactive mode and sat waiting for terminal input that the runner never provides
- A. Correct. Without -p (--print), Claude Code launches its interactive TUI, which blocks waiting on stdin. CI runners provide no interactive terminal, so the process hangs indefinitely until the job times out.
- B. An invalid or missing API key produces an explicit authentication error in the log almost immediately, not a silent indefinite hang with zero output.
- C. A shallow clone can limit git history available to Claude, but it does not cause a process to hang waiting on input; Claude would still print output or an error.
- D. An incompatible Node/runtime version typically causes the CLI to fail fast with a startup error, not hang silently through the entire job timeout.
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?
dontAsk denies anything that would prompt, and the docs call it useful for locked-down CI runs. Calls covered by --allowedTools still go through.
“dontAsk: Claude Code denies every call that would otherwise prompt, which is useful for locked-down CI runs.”Source: code.claude.com
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.
These are the three formats in the headless docs. Only json returns one object you can query with jq after the run.
“json: structured JSON with result, session ID, and metadata”Source: code.claude.com
--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:
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 ' ? 'Without a schema, the answer is the text in .result. The .structured_output field holds output matching a schema passed with --json-schema.
Source: code.claude.comSources2
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:
- 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?
Correct answer: A — In a CLAUDE.md file at the repository root describing testing standards, the valuable-test criteria, and available fixtures
- A. Correct. CLAUDE.md is the mechanism Claude Code reads automatically for project context such as testing standards, fixture conventions, and review criteria, so it persists across every CI-invoked run without re-specifying it.
- B. Duplicating standards inline in every workflow file means any update requires editing multiple YAML files and risks drift between jobs, unlike a single shared CLAUDE.md.
- C. PR description templates depend on contributors manually retyping guidance, which is unreliable and does not guarantee Claude reads it during an automated CI run.
- D. Hardcoding standards into a shell profile ties them to a specific runner's environment rather than the repository, so they would not travel with the codebase or apply consistently across runners.
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?
The Action starts the inline-comment MCP server only when --allowedTools in claude_args names it. The skill's frontmatter is not enough.
“the Claude Code GitHub Action starts the MCP server that posts inline comments only when --allowedTools in claude_args names it”Source: code.claude.com
Sources3
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
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.
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.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.https://code.claude.com/docs/en/cli-referenceOfficial docs
“Query via SDK, then exit”
↩︎ The -p flag: one prompt in, one answer out“Process piped content”
↩︎ The -p flag: one prompt in, one answer out“Query via SDK, then exit”
↩︎ Key concept - 2.https://code.claude.com/docs/en/headlessOfficial docs
“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“text (default): plain text output”
↩︎ Machine-readable results: --output-format json and --json-schema“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 - 3.https://code.claude.com/docs/en/github-actionsOfficial docs
“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