What you will be able to do
- Run Claude Code non-interactively with -p so a pipeline job starts, finishes and exits on its own
- Choose permission flags that stop a CI run from waiting on an approval nobody will give
- Use --output-format json with --json-schema to get findings a script can parse and post as PR comments
Key concept
Headless (non-interactive) run — With the -p flag (long form --print), Claude Code takes one prompt, does the work, writes the result to stdout and exits. It never opens an interactive session. Every CI integration in this subdomain is built on that one-shot, scriptable run.
1.From a chat session to a pipeline step
Run plain claude and you get an interactive session: it waits for you to type, shows its work and asks before risky actions. A CI runner has no keyboard and nobody watching, so it needs a different contract. The job should hand over a prompt, get an answer back and move on to the next step. The -p flag (long form --print) gives you that contract. The CLI reference describes claude -p "query" as a way to query and then exit, and it lists piped input (cat file | claude -p "query") as a supported way to work.
Because the run reads stdin and writes stdout, it fits into ordinary shell plumbing. A build log can go in and a written explanation can come out, with no human involved at any point.
cat build-error.txt | claude -p 'concisely explain the root cause of this build error' > output.txtThis is the same mechanism hosted integrations use. The GitLab setup adds a job to .gitlab-ci.yml that installs Claude Code and runs claude -p with the request as the prompt. When the GitHub Action receives a prompt input, it switches to automation mode and runs without waiting for anyone to mention @claude. On every platform, the building block is a single -p invocation.
2.Why -p alone does not prevent a hang
-p removes the chat, but it does not remove permission checks. A -p run starts in the default permission mode, and in that mode only reads go ahead without asking. An edit or a shell command still needs an approval. The headless docs include this as a real process state, a session left waiting for an answer to a permission prompt, and they note that sending SIGTERM at that point leaves the prompt unanswered. Nobody on a CI runner will ever answer it.
To stop the hang, decide ahead of time what the run is allowed to do, using flags on the command line:
- --allowedTools pre-approves specific tools or command patterns, such as "Read" or "Bash(npm test)". Treat the text inside the parentheses as a pattern, not a label. The approval covers every command the pattern matches, so make it no wider than the job needs.
- --permission-mode dontAsk handles everything else: any action that would have prompted is denied instead. The run fails fast rather than stalling.
- --permission-mode acceptEdits is the looser choice when the job's purpose is to change files.
claude -p "run the test suite" --permission-mode dontAsk --allowedTools "Bash(npm test)" "Read"| Mode | What runs without asking | Fit for CI |
|---|---|---|
| default | Reads only | This is where a -p run starts. Any non-read action needs approval nobody can give |
| acceptEdits | Reads, file edits, and common filesystem commands | Jobs that are meant to change code, as in the GitLab example |
| dontAsk | Reads and pre-approved tools; anything that would prompt is denied | Locked-down CI and scripts |
| bypassPermissions | Everything | Isolated containers and VMs only |
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.
3.Machine-parseable results: --output-format and --json-schema
A run that no longer hangs still needs output a program can read. By default a headless run prints plain text, which works for a log but not for a script that has to post comments. --output-format offers three shapes:
| Value | What you get |
|---|---|
| text | Plain text output (the default) |
| json | Structured JSON with result, session ID, and metadata |
| stream-json | Newline-delimited JSON for real-time streaming |
--output-format json alone wraps the answer in an envelope, but the answer itself is still free text in the result field. To make the content follow a fixed shape, add --json-schema with a JSON Schema. The data that matches the schema appears in a separate structured_output field. The headless docs read the two fields differently: jq -r '.result' extracts the text, and jq '.structured_output' extracts the schema-shaped data.
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'Use -p with --output-format json and --json-schema. The schema is one you design, for example a required array of findings, each with a file, a line and a message. The script reads .structured_output. The .result field only holds the text answer.
Sources4
4.From findings to inline PR comments
Put the pieces together and you have a review bot. The headless docs show the input side: pipe the pull request diff into claude -p, give the run a reviewer role with --append-system-prompt, and ask for JSON. Add a --json-schema that describes findings, and a later step can loop over structured_output and post each finding as an inline comment on the right file and line.
gh pr diff "$1" | claude -p \
--append-system-prompt "You are a security engineer. Review for vulnerabilities." \
--output-format jsonThere is also a built-in route. The /code-review skill accepts --comment, which posts findings on a GitHub pull request as inline comments. Skills can be invoked inside a -p prompt, and in non-interactive mode Claude Code waits for the review to finish and includes the findings in the response. On GitHub specifically, the Claude Code GitHub Action runs in automation mode whenever the workflow supplies a prompt input. Results go to the workflow run log unless the prompt tells Claude to post and gives it a tool that can.
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.
Sources6
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.Adding -p is enough to make Claude Code safe to run unattended, because non-interactive mode never waits for input.Why is that wrong?
A -p run starts in default mode, where only reads run without asking. Edits and commands still need approval, and the process can sit waiting on a permission prompt. Pre-approve tools with --allowedTools, or use dontAsk so that prompts become denials.
Covered in Why -p alone does not prevent a hang
2.--output-format json on its own makes Claude return findings in whatever JSON shape the pipeline needs.Why is that wrong?
json only adds an envelope: result, session ID and metadata. The answer in result is still text. A fixed shape needs --json-schema, and that data is read from structured_output.
Covered in Machine-parseable results: --output-format and --json-schema
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”
↩︎ From a chat session to a pipeline step - 2.
“runs once and prints the result. Good for CI hooks, pre-commit checks, or piping into other tools.”
↩︎ From a chat session to a pipeline step“runs once and prints the result. Good for CI hooks, pre-commit checks, or piping into other tools.”
↩︎ Key concept - 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”
↩︎ From a chat session to a pipeline step - 4.https://code.claude.com/docs/en/headlessOfficial docs
“Waiting for an answer to a permission prompt: if you send SIGTERM to the process, Claude Code leaves the prompt unanswered.”
↩︎ Why -p alone does not prevent a hang“json: structured JSON with result, session ID, and metadata”
↩︎ Machine-parseable results: --output-format and --json-schema“json: structured JSON with result, session ID, and metadata”
↩︎ Exam trap 2 - 5.https://code.claude.com/docs/en/permission-modesOfficial docs
“Reads and pre-approved tools; anything that would prompt is denied”
↩︎ Why -p alone does not prevent a hang“Reads and pre-approved tools; anything that would prompt is denied”
↩︎ Exam trap 1 - 6.https://code.claude.com/docs/en/code-reviewOfficial docs
“--comment: posts the findings on a GitHub pull request as inline comments, or on a GitLab merge request as a single note”
↩︎ From findings to inline PR comments“Claude Code waits for the review and includes the findings in the response”
↩︎ From findings to inline PR comments