IDInternals Decoded
Claude Code, Mastered
PlaybooksIntermediate11 min readJun 2026

Headless Claude Code: CI, Scripts, and Automation

Run the agent without the terminal UI: nightly fixes, PR reviews, batch jobs.

Part 9 of 10Claude Code, MasteredView series →

In the last episode, we saw how Claude Code remembers your project across sessions. Now we take that persistent agent out of the terminal and into your CI pipelines.

Headless Claude Code runs the same agent that edits your code and runs tests, but without an interactive UI. A prompt, a configuration, and a set of permission rules replace the human. The agent invokes tools, applies edits, and emits results as structured output for scripts or CI jobs to consume. This is the same agent loop, the same project context, and the same safety checks you use in local development, just driven programmatically.

The surprising part: you can run it in a container on a CI runner, under the exact same permission policies you set for your team, and it will never ask for a human confirmation. Instead, it uses a classifier and a set of pre-approved rules to decide what is safe. That means you can automate PR reviews, nightly fixes, and batch code transformations without ever building a separate agent.

How does Claude Code run without a terminal UI?

The claude CLI (command-line interface) has a flag that tells it to skip the interactive REPL. That flag is -p, short for “print mode.” When you run claude -p "Explain this code", the CLI reads the prompt, discovers your project context, and runs the agent to completion, then prints the final answer to stdout.

Interactive vs Headless Mode
Interactive Mode
  • Terminal UI with REPL
  • Human approves each action
  • Real time feedback
Headless Mode (claude -p)
  • No terminal UI
  • Pre configured permissions
  • Output to stdout or file
Headless mode skips the interactive REPL and uses a flag to run the same agent directly.

Everything else is the same. The CLI loads your CLAUDE.md, your permission settings, your MCP (Model Context Protocol) server configurations, and any hooks or skills. It then starts the Agent SDK (software development kit), which manages the conversation loop. The only difference is that input comes from the -p argument and any piped-in stdin, and output goes to stdout or a file, not to a chat panel.

You can pipe a file straight into the agent. For example, cat src/app.js | claude -p "Find security issues" feeds the file content as part of the conversation. The CLI appends it to the initial prompt, just like a user pasting code. This makes -p perfect for scripts that need to analyze a specific file or patch.

The --output-format flag controls how the result is delivered. text prints the agent’s plain response. json emits a structured object with the result text, session id, and metadata. stream-json sends newline-delimited JSON (JavaScript Object Notation) fragments as the agent works, so you can tail progress in real time. These options let you integrate Claude Code into any pipeline that can parse JSON or plain text.

How does the Agent SDK orchestrate headless runs?

The Agent SDK is the same engine that powers Claude Code in editors and the web UI. In headless mode, you bypass the visual layer and call the SDK directly. You can do this via the CLI, or through the Python and TypeScript SDKs that ship as part of the tool.

The SDK does not simply wrap the Messages API (application programming interface). It implements the full agent loop: it builds the system prompt, manages tool schemas, handles tool call dispatch, enforces permissions, and maintains context across turns. When you run a task headlessly, the SDK handles all of that for you. You provide a prompt, a working directory, and a few configuration knobs. The SDK returns the final result and telemetry.

Here is a simplified view of the headless agent loop:

The SDK also tracks token usage. Every task run returns the number of input and output tokens consumed, plus any tool-related costs. You can use this to enforce budget limits in CI or to bill back to teams. Because the SDK is the same code that runs locally, any improvements to planning, tool selection, or context management flow into your headless runs automatically.

Agent SDK Headless Run
5k
Avg input tokens
1.2k
Avg output tokens
3
Tool calls per task
The SDK tracks token usage and tool costs for every task (illustrative values).

How do permissions work when there is no human to approve?

In interactive mode, the agent asks you before it edits files, runs shell commands, or makes network calls. In headless mode, you must pre-configure which actions are allowed. Claude Code enforces these rules in the runtime, not in the model.

Permissions are defined by a set of rules and a permission mode. Rules can allow, deny, or ask for specific tools. For example, you can allow the bash tool only for npm test and deny all other commands. Modes determine how often the agent needs to pause for confirmation. The safest modes for CI are dontAsk and auto.

In dontAsk mode, any tool that is not explicitly allowed is denied without prompting. This is ideal for pipelines that need strict determinism, like a job that only reviews code and never modifies it. In auto mode, a classifier runs in the background and decides whether a proposed action is safe. The classifier uses a list of trusted repositories, buckets, and domains you define in autoMode.environment. If an action targets something outside that list, the classifier treats it as potentially dangerous and blocks it.

Our side project’s CI pipeline uses auto mode. We configured the environment to trust only our own GitHub repository and our staging API domain. That means the agent can read the codebase, run linters, and post comments on a pull request, but it cannot push to an external service or read arbitrary URLs. Even if the model hallucinates a dangerous command, the runtime will stop it.

You can also layer on PreToolUse hooks. These hooks run custom shell commands before every tool call, giving you a chance to inspect arguments, log them, or integrate with an external policy engine. For regulated environments, hooks provide an audit trail of every action the agent attempted.

How can I use Claude Code in CI/CD pipelines?

The simplest path is to install the claude CLI in your CI runner and call it with -p. Both GitHub Actions and GitLab CI/CD (continuous integration and continuous delivery) support this pattern. You check out your repository, set up authentication, and run the agent as a step.

For our side project, we added a GitHub Actions workflow that runs on every pull request. It checks out the PR branch, installs the CLI, and calls:

claude -p "Review this PR diff for bugs and style issues. Output a summary as JSON."

The agent reads the diff, runs any linters we allow, and returns a JSON object with a summary and a list of suggestions. A subsequent step posts that JSON as a comment on the PR.

You can also use the agent to fix issues automatically. We have a nightly job that runs claude -p "Fix all ESLint errors in the latest commit and create a pull request". The agent edits files, commits the changes, and opens a PR. Because we set dontAsk mode with a rule that allows git commands and file writes, the job runs without any human intervention.

The key is to keep the agent’s scope limited. We never allow direct pushes to main. The agent always works on a feature branch and opens a PR. That way, a human still reviews the change before it merges.

What tools can Claude Code use in a headless environment?

The agent can use any tool you register with it, just like in interactive mode. Built-in tools include bash for shell commands, text_editor for file operations, and mcp__* for MCP servers.

Our side project uses the bash tool to run npm run lint, npm test, and git commands. We also configured an MCP server that connects to our Jira instance. The agent can query open issues and link PRs to them, all within the headless run.

MCP servers are defined in a .mcp.json file at the repo root. The CLI auto-discovers this file and loads the servers. In headless mode, you can also pass --mcp-config to point to a specific config. Once loaded, the MCP tools appear in the agent’s tool palette, subject to the same permission rules.

You can also write custom client tools in Python or TypeScript and register them with the SDK. For example, we built a small tool that queries our feature flag service, so the agent can check whether a feature is enabled before suggesting a change. The tool runs inside the CI container, just like bash, and its output is fed back to the model.

All tool calls are wrapped in permission checks. Even custom tools must be allowed in the permission ruleset. This ensures that a misconfigured tool cannot bypass your safety policies.

How does output streaming work for automation?

The stream-json output format is designed for long-running tasks. Instead of waiting for the final answer, the CLI emits newline-delimited JSON objects as events happen. Each event represents a message start, a content delta, a tool call, or a tool result.

You can pipe these events into a logging system, a dashboard, or a monitoring tool. For our nightly job, we stream the JSON to a simple Node.js script that updates a Slack channel with progress: “Agent is running linter..”, “Agent found 3 issues”, “Agent is applying fixes..”. This keeps the team informed without flooding the channel with raw logs.

The event structure is similar to the Messages API’s streaming SSE format. A typical flow starts with a message_start event, followed by a content_block_start for each tool use or text block, then content_block_delta events with incremental content, and a content_block_stop. Finally, a message_delta and message_stop signal the end.

Your CI step can wait for the message_stop event and then parse the final result from the accumulated JSON, or it can process events as they arrive. This gives you full control over how you consume the agent’s output.

Quick Reference

PropertyValue
Headless entry pointclaude -p <prompt>
Output formatstext, json, stream-json
Permission modesdefault, acceptEdits, dontAsk, auto
Auto mode environment settingautoMode.environment in settings
MCP config file.mcp.json at repo root
Maximum turns control--max-turns <N>
Bare mode (no auto-discovery)--bare
Token trackingincluded in SDK response and --output-format json

Test yourself

Your CI job runs Claude Code headlessly to fix linting errors and open a PR. It fails with a permission denied error when the agent tries to run git push. The error says: “Tool bash with argument git push is not allowed by the current permission mode.” How do you fix this without switching to a fully permissive mode?

Answer: Add a specific allow rule for the git push command in your permission settings. You can scope it to the exact branch pattern the agent uses, like git push origin feature/auto-fix-*. Then set the permission mode to dontAsk or auto to avoid prompts. If you use auto, ensure the target remote is listed in autoMode.environment as a trusted repository. This lets the agent push only to the intended branch while still blocking pushes to main or any other protected branch. Keep the dontAsk mode so that no other commands slip through without explicit rules.

Frequently Asked Questions

Q: Can I run Claude Code in a Docker container without a display? Yes. The CLI has no GUI dependency. Install it in the container, set the ANTHROPIC_API_KEY environment variable, and invoke claude -p. For enterprise setups, you can point it to Amazon Bedrock or Google Vertex AI instead.

Q: What permission mode is safest for a CI pipeline that only reads code? Use dontAsk mode with a ruleset that allows only read-only tools like bash with a whitelist of safe commands (e.g., cat, ls, grep). Deny all file writes and network access. This ensures the agent cannot modify anything.

Q: How do I configure auto mode to trust my organization’s repositories? Set the autoMode.environment field in your settings to a list of trusted repository URLs, S3 buckets, and domains. For example: ["https://github.com/my-org/*", "https://my-staging.example.com"]. The classifier will allow actions that target these and flag anything else.

Q: How can I prevent Claude Code from making network calls to external services? Define a deny rule for the web_fetch tool and any MCP servers that access external APIs. In dontAsk mode, any attempt to use a denied tool will fail. In auto mode, the classifier will block requests to domains not in your trusted list.

Q: Can I use Claude Code with self-hosted LLMs via Bedrock or Vertex AI? Yes. The Agent SDK supports multiple providers. When you initialize the agent, you specify the provider and the model ID. For Bedrock, you’ll need AWS credentials; for Vertex AI, you’ll need a GCP service account. The headless flow is identical regardless of the backend.

Keep going deeper

If you want this kind of breakdown every week, how real systems actually work under the hood, subscribe to Internals Decoded at internalsdecoded.com. Next episode, we’ll look at advanced deployment patterns: running Claude Code as a long-lived service, wiring it into webhooks, and building custom agents with the SDK.

Sources

#headless-claude-code#ci-automation
More from the library
The Newsletter

Keep up with AI. One email a week.

One thoughtful email each week. Unsubscribe whenever you like.