Running Claude Code headless in CI
The shift from interactive to scripted
Claude Code's normal mode assumes a person watching the output and answering permission prompts as they come up. A CI pipeline has neither — nothing is watching in real time, and nothing can answer an interactive prompt, so a run configured the way you'd use it at your own terminal simply hangs the first time it hits something that would normally wait for a human. Headless mode exists for exactly this gap: a non-interactive invocation that runs a task to completion (or failure) and exits with output a pipeline can parse, no human in the loop required.
Permissions have to be decided in advance, not in the moment
The single biggest adjustment moving into CI is that every permission decision has to be settled before the run starts, because there's no one there to answer a prompt mid-run. That means being considerably more deliberate about what the CI invocation is allowed to do than you might be at your own terminal, where you can eyeball a request and approve it in the moment. Scope a CI run's permissions to exactly what the task needs — running tests, reading the repository, writing to a specific output location — and nothing broader, since there's no human safety net left to catch an overly broad grant behaving unexpectedly.
Structured output matters more than it does interactively
At a terminal, you read Claude Code's output as prose and judge for yourself whether it succeeded. A pipeline needs something it can parse programmatically to decide whether to pass or fail the build, so structured output — a machine-readable result rather than free-form text — is worth configuring explicitly for a CI run even if you never bother with it interactively. Treat the exit code and the structured result as the actual contract your pipeline depends on, and don't rely on scraping prose output for a success/failure signal — that's fragile in a way a structured result isn't.
Cost and time budgets need a hard ceiling
An interactive session has a natural brake: a person watching it happen who'll notice if it's going somewhere expensive or unproductive and stop it. A CI run has none of that unless you build it in. Set an explicit ceiling — on effort level, on iteration count, on wall-clock time — for anything that runs unattended and repeatedly, since a headless run stuck in an unproductive loop on a bad day burns through both time and spend with nobody watching until someone notices the bill or the stuck pipeline.
What to actually automate this way
The workloads that suit headless Claude Code well are ones with a clear, checkable success condition — a test suite that either passes or doesn't, a lint pass with an unambiguous result, a scoped code-review pass against a fixed rubric. Open-ended tasks with no clear stopping point are a worse fit for unattended automation regardless of how well you've configured the run, because nothing in the pipeline can tell the difference between "still working productively" and "spinning without making progress" the way a person watching interactively could.
Logging what happened, not just whether it passed
An interactive session leaves a full transcript in front of the person running it; a headless CI run leaves only whatever you chose to capture. If a headless run fails in a way that needs investigating later, a bare pass/fail exit code tells you nothing about why — capture the structured result and enough of the run's reasoning to reconstruct what happened without having to reproduce the failure locally first. This matters more for CI specifically than for interactive use, because nobody was watching it happen the first time.
Related
For the permission-scoping decisions this depends on, see configuring Claude Code permissions without fighting them, and for the config surface a CI invocation reads from, see the Claude Code config file, field by field.
Verified 2026-08-08 against ClaudeHow facts module (src/data/facts/) — see /about/#accuracy.