# How to Run Claude Code Headless in a Script or CI

> Run Claude Code non-interactively with claude -p, get JSON back, pre-approve tools, and wire it into a CI job. Copy-paste.

- Canonical: https://guides-ai.pages.dev/guides/claude-code-headless-ci/
- Plate 04.05 · Topic: Workflows (https://guides-ai.pages.dev/topics/workflow/)
- Published: 06 Sept 2026 · 4 min read
- Source site: guides-ai — https://guides-ai.pages.dev/

`claude -p` runs a single prompt and exits. No TUI, no approval prompts to click — which is
exactly what a script or a CI job needs.

## 1. One prompt, one answer

```bash
claude -p "What does the auth module do?"
```

`-p` (long form `--print`) is the whole trick. Claude Code exits with code 0 on success and
non-zero when the run fails, so your script can branch on `$?`.

It also reads stdin, so it composes like any other command-line tool:

```bash
cat build-error.txt | claude -p 'concisely explain the root cause of this build error' > output.txt
```

## 2. Pre-approve the tools it needs

Without permission to act, a headless run can only talk. `--allowedTools` grants tools up front:

```bash
claude -p "Run the test suite and fix any failures" \
  --allowedTools "Bash,Read,Edit"
```

You can scope Bash down to specific commands using permission rule syntax:

```bash
claude -p "Look at my staged changes and create an appropriate commit" \
  --allowedTools "Bash(git diff *),Bash(git log *),Bash(git status *),Bash(git commit *)"
```

The space before `*` matters: `Bash(git diff *)` matches `git diff …`, while `Bash(git diff*)`
would also match `git diff-index`.

## 3. Get JSON out

```bash
claude -p "Summarize this project" --output-format json | jq -r '.result'
```

`--output-format` takes `text` (default), `json` (result plus session ID, cost, and metadata),
or `stream-json` (newline-delimited events). The JSON payload includes `total_cost_usd`, so a
script can log spend per invocation.

## 4. Harden it for CI

Add `--bare`. It skips auto-discovery of hooks, skills, custom commands, subagents, plugins,
MCP servers, and `CLAUDE.md`, so the run behaves the same on every machine instead of picking
up whatever is in the repo or a teammate's `~/.claude`. Bare mode doesn't use your subscription
login, so set `ANTHROPIC_API_KEY`:

```bash
claude --bare -p "Summarize README.md" --allowedTools "Read"
```

A wrapper in `package.json` turns this into a project-specific reviewer:

```json
{
  "scripts": {
    "lint:claude": "git diff main | claude -p \"you are a typo linter. for each typo in this diff, report filename:line on one line and the issue on the next. return nothing else.\""
  }
}
```

---

**Tip:** in an unattended job, nobody can answer a permission prompt. Pass
`--permission-prompts none` so anything that would prompt is denied and the run continues
instead of hanging.

Next: [set up the Claude Code GitHub Action](/guides/claude-code-github-actions/) or
[tighten Claude Code permissions](/guides/claude-code-permissions/).
