Claude Code headless mode: turn the agent into a shell command
The build fails, and you perform the ritual: copy the log, open a session, paste it, type "what broke?", read the answer, close the tab. Somewhere around the fortieth repetition it lands: you are the pipe. Two programs are talking through your clipboard, and one of them is a text-processing tool pretending it needs a meeting with you.
Claude Code has a flag for this. Add -p and the agent stops being a chat
you attend and becomes a shell command that runs once, prints its result,
and exits. That one change moves it into a category your tooling already
understands: pipeable, schedulable, callable from any script you own.
What Claude Code headless mode actually is
claude -p "your task" runs the full agent, tools and all, with no
interface attached. It reads stdin, so
cat build-error.txt | claude -p "explain the root cause" does what you
hope, and it writes stdout, so > and | behave. The
official docs call this running
Claude Code programmatically, which is the right frame: the agent as a
function.
The key property: headless mode makes the agent composable. An
interactive session bolts the model to your attention; a headless run bolts
it to your toolchain. git log can feed it, jq can parse it, cron can
trigger it, CI can gate on it.
A few flags do the plumbing:
--output-format jsonreturns the result plus metadata, including what the run cost.--json-schemaforces output into a shape you define, which turns "please answer in JSON" from a hope into a contract.--allowedToolsand--permission-modedecide up front what the run may do; nobody is at the keyboard to approve.--bareskips your local hooks, skills and instruction files, so the command behaves the same on every machine.
The idea is not exclusive to one vendor: Codex has codex exec, and Gemini
CLI takes a prompt flag the same way.
Three places a scripted agent earns its keep
In a pipe. One-shot transforms with a clear input: a failing build log
into a root cause, a week of git log into release notes, flaky test
output into a ranked list of suspects. If the job is "turn this text
into that text," it never needed a conversation.
In your repo's scripts. A package.json script or Makefile target that
pipes git diff main through a picky reviewer prompt gives every developer
the same check with zero setup. This is the mirror image of
Claude Code hooks: hooks put your shell commands
inside the agent's loop, headless mode puts the agent inside your shell
scripts. How to brief that reviewer is a craft of its own:
make an agent your first reviewer covers it.
In CI. A pipeline step that reviews the diff, labels the PR, or drafts
the changelog entry, with JSON output feeding jq so the next step can
act. Anthropic's own
best practices guide
describes teams fanning headless runs across issue triage and large
migrations exactly this way.
When a script beats a session
The dividing line: if you can write the whole ask down before the run starts, and something other than you can judge the result, script it. If the ask will change the moment you see the first attempt, keep it interactive.
Scripts win when the task repeats on a trigger (every push, every night, every new issue), when another program consumes the output, and when one prompt runs across many inputs. A loop over thirty files with one instruction is a script; nobody should type it thirty times.
Sessions win for anything exploratory: debugging an unknown, shaping an API, any work you would steer after seeing the first attempt. A headless run cannot be steered, only re-briefed and re-run from zero.
Where headless runs go wrong
The honest section, because unattended one-shots fail in their own ways:
- The permission flag becomes a bypass valve. A headless run cannot
ask, so the first friction tempts people toward blanket skip-everything
flags. In CI, that hands an agent your repo and network on the say-so of
whatever text landed in its prompt. Grant narrow rules instead, like
--allowedTools "Bash(git diff *)", and treat guardrails as part of the script, not an obstacle to it. - Pipelines assume functions; models are not functions. The same input can produce a differently worded output tomorrow. A schema pins the shape, not the judgment. Keep each run small, and let tests decide whether the result ships.
- Green output stops being read. The nightly review job nobody opens is quietly rotting. The habits from reviewing agent work apply to automated output most of all: sample it on a schedule.
- Cost multiplies by cron. A cheap run, multiplied by every push and every repo, is a real line item. The JSON output reports each run's cost; logging it ends the mystery.
- Your laptop leaks into the script. Without
--bare, a headless run picks up your local instruction files, hooks and MCP servers, so the same script can behave differently on a teammate's machine. Reproducible runs declare their context explicitly.
Scripts for the verbs, a desk for the judgment
Headless mode is the right tool when the result is machine-checkable. But a lot of recurring agent work does not end in an exit code; it ends in a diff a human has to look at. A cron entry cannot look at anything, and a folder of last night's output is nobody's favorite review queue.
That review layer is what SanuDesk is built for. It is a desktop app that tiles your Claude Code, Codex and Gemini sessions into one grid, next to a Kanban board that does the dispatching: write the task as a card, deploy it to a fresh session with one click, and when the agent stops, the card parks itself in a review lane with a note on what changed and how to test it. The recurring chores that outgrow a one-shot pipe become Loops: the same brief fired on a schedule, every run journaled, with your real environment available, so the job that needs your database just works. It is bring-your-own-model, driving the agent subscriptions you already pay for, and the grid and board are in the free plan (details on pricing).
The split is clean: pipe the transforms, schedule the chores into a review lane, and keep a live session for the work that needs steering.
Pipe one real thing through it today
The experiment is one line, run in a repo with changes on a branch:
git diff main | claude -p "You are a picky reviewer. For each real problem \
in this diff, print file:line and a one-line issue. If it is clean, print \
CLEAN and nothing else."
If it prints CLEAN, good news. If it catches something, you just built a reviewer that costs one line and runs anywhere your shell does. Promote it to a repo script so the whole team gets it, and the next time you catch yourself pasting output into a chat window, notice the moment: that is a pipe asking to exist.
Download SanuDesk free to give the work that needs human eyes a grid and a review lane, or see how the board, grid and Loops fit together on the features page.