Claude Code's non-interactive mode (claude -p) runs the same agent loop as an interactive session, but nobody is watching to click "Allow." In CI, that means every permission decision has to be made in advance, either by pre-approving specific tools or by explicitly denying everything else.
claude -p in any runner: a shell step that pipes in a prompt (or a diff, a log file) and reads the result. Works on GitHub Actions, GitLab CI, or a cron box.anthropics/claude-code-action): install once, and it responds to @claude mentions on issues/PRs, or runs a fixed prompt on any GitHub event including a schedule.claude -pAdd --bare in CI so the run doesn't pick up a contributor's local hooks, MCP servers, or CLAUDE.md and produce a different result on every machine. Bare mode skips your OAuth login too, so set ANTHROPIC_API_KEY instead:
export ANTHROPIC_API_KEY=sk-ant-...
claude --bare -p "Run the test suite and summarize any failures" \
--allowedTools "Bash(npm test),Read" \
--output-format json
--allowedTools is the whitelist: only the tools and command prefixes listed run without a prompt. Anything not covered is denied outright in bare mode's default (Manual) permission behavior — there's no one there to answer a prompt, so an unlisted tool call just fails.
For a fully unattended run, pair a permission mode with --permission-prompts none so nothing waits on an answer that will never come:
claude --bare -p "Update dependency pins and run the tests" \
--permission-mode auto \
--permission-prompts none \
--output-format json
With the flag set, anything that would have prompted is denied instead of hanging, and Claude is told not to retry it. Permission rules and the mode you pick are still checked first — the flag only resolves what's left over.
--output-format json returns a single JSON object with the result text, session ID, and cost. Pull the pieces you need with jq:
result=$(claude --bare -p "Summarize this PR's risk" --output-format json)
echo "$result" | jq -r '.result'
cost=$(echo "$result" | jq -r '.total_cost_usd')
Claude Code exits 0 on success and non-zero on failure, so a CI step can branch on $? without parsing output at all. A SIGTERM (a job timeout, for instance) exits with code 143 and leaves the in-progress turn unrecorded.
For GitHub specifically, anthropics/claude-code-action wraps the same underlying SDK and handles auth, the @claude trigger phrase, and posting results back to the PR. Run /install-github-app from Claude Code locally for guided setup, or add the workflow by hand:
name: Claude Code
on:
issue_comment:
types: [created]
jobs:
claude:
if: contains(github.event.comment.body, '@claude')
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
Store the key as a repository secret, never inline in the workflow file. With no prompt input, the action waits for @claude mentions (interactive mode). Add a prompt input and it runs unattended on any trigger, including schedule (automation mode) — that mode still checks that the triggering actor has write access before doing anything.
Pass CLI flags through claude_args, the same way you'd pass them to claude -p directly:
claude_args: |
--max-turns 5
--model claude-sonnet-5
--allowedTools "mcp__github__list_commits,mcp__github__list_issues"
--max-turns caps how much a single run can do, which limits both runaway loops and their bill. Keep the workflow's own GitHub token permissions narrow too — a review job that only reads PRs doesn't need contents: write.
| Layer | What it stops |
|---|---|
--allowedTools / permissions.allow | Tools and command prefixes outside the list |
--permission-prompts none | Runs hanging on a prompt nobody can answer |
A PreToolUse hook | A destructive pattern inside an otherwise-allowed command, like Bash(npm run *) matching npm run reset-db -- --force |
An allowlist entry like Bash(git *) also approves git push --force; see Claude Code permissions for narrower rule syntax, and blocking dangerous commands for a hook that catches destructive commands a prefix rule would otherwise wave through. The free hook builder generates that kind of guard script from a plain-language description if you'd rather not write the regex by hand.
A CI job that runs on every push, times out slowly, or lacks --max-turns can rack up cost quietly in the background. Reducing Claude Code token usage covers picking cheaper models for mechanical CI tasks and reading per-run cost out of --output-format json.
Keelwork bundles 10 workflow skills, 5 tested safety hooks (including a full guard-bash and a secret scanner), 3 subagents and 5 CLAUDE.md templates, with a one-command installer that safely merges into your settings.
Get Keelwork — $24 →