OmniscioGuidesHeadless mode
Claude Code headless mode, and why a claude -p run hangs, asks or prints nothing
Print mode is the -p flag. It runs Claude Code once, takes a prompt from the argument or from stdin, and writes the answer to stdout, so a script or a CI job can call it like any other command-line tool. This page covers the flags that matter in that setting, the three output formats and the result they produce, and then the three things people arrive looking for: a run that hangs, a run that asks for permission, and a run that finishes with nothing to show.
| You want | Do this |
|---|---|
| One answer, printed | claude -p "your prompt", which exits 0 on success and non-zero when the run fails |
| Output a script can read | Add --output-format json and read the result field |
| A job that never waits on a person | Set --allowedTools or a permission mode, because a call with no rule is denied rather than prompted |
| A run that cannot loop or overspend | Add --max-turns and --max-budget-usd |
| You want | Read this |
|---|---|
| To understand what the flag does | What print mode is |
| The flags worth knowing in a script | The flags that matter |
| To parse the output, or trust the exit code | Output formats and exit codes |
| To set permissions for an unattended run | Permissions in scripts and CI |
| To find a run that asks, or waits, on a person | Why it asks for permission |
| To find a run that never finishes | Why a headless run hangs |
| To find a run that finished with nothing | Why a run exits with no output |
| To know what this page leaves to another | Where this page stops |
Checked against Claude Code 2.1.310 documentation, October 25, 2026. By the Omniscio team. Updated .
What print mode is
Adding -p, or its long form --print, runs Claude Code non-interactively. Anthropic is explicit that not every option combines with it: Claude Code rejects --bg, and it rejects --cloud with a task description by naming the conflict, while --cloud with a session ID queues a message into that cloud session and exits instead. Everything else on this page is about the options that do combine with it.
The options Anthropic says you will often combine with -p are --continue for carrying a conversation on, --allowedTools for letting tools run without asking, and --output-format for structured output. Those three show up again in the sections below.
What the run reads is worth knowing before the flags. Non-interactive mode reads stdin, so data can be piped straight in, and the response can be redirected out. Without --bare, a print-mode session also discovers and loads what an interactive session would: hooks, skills, custom commands, subagents, installed plugins, MCP servers, auto memory and CLAUDE.md, including anything in the working directory or in your own configuration folder. It also runs the hooks in a project's settings file and connects the servers in its .mcp.json even in a folder that was never trusted, and it shows neither a trust dialog nor a per-server approval prompt.
That is what --bare is for. Anthropic calls it the recommended mode for scripted and SDK calls and says it will become the default for -p in a future release. It skips that discovery, with one documented exception: a directory named with --add-dir still contributes its .claude/skills/ folder. So a hook in a teammate's home folder or an MCP server in the project's .mcp.json will not run, which is what makes a CI job behave the same on every machine. The cost is one thing worth knowing before adopting it: bare mode never reads OAuth credentials or the system keychain, so a subscription login does not carry into it. It needs an API key in the environment, or an apiKeyHelper supplied through --settings.
The flags that matter in a script
Most of the flag list is irrelevant to a script and a few of them matter a great deal. These are the ones this page's subject turns on.
| Flag | What it decides |
|---|---|
-p, --print | Run once, non-interactively, instead of opening the terminal interface |
--output-format | text, json or stream-json |
--input-format | Whether stdin carries plain text or newline-delimited stream JSON |
--allowedTools | Which tools run without asking, written in the same rule syntax as settings files |
--permission-mode | The baseline for the whole run, such as auto, dontAsk or acceptEdits |
--permission-prompts | Whether a run with a permission host may wait on it for an answer |
--permission-prompt-tool | Names an MCP tool that answers permission requests for the run |
--max-turns | How many agentic turns the run may take before it stops with an error |
--max-budget-usd | Stops the run once estimated spend reaches that amount |
--json-schema | Asks for output conforming to a JSON Schema, returned in structured_output |
--bare | Skips that discovery, apart from the skills folder of a directory named with --add-dir |
The one piece of syntax that catches people is inside --allowedTools. The value uses the same rule syntax as a settings file, and a trailing space and star turns a rule into a prefix match, so Bash(git diff *) allows any command starting with git diff. The space before the star is load-bearing: written as Bash(git diff*) it also matches git diff-index.
The two bounds are worth setting on any job that loops. --max-turns limits the number of agentic turns and exits with an error when the limit is reached, with no limit by default. --max-budget-usd stops the run once estimated spend reaches the amount you name, with two caveats Anthropic states plainly: the cap is checked against a client-side cost estimate that can differ from your bill, and spend can pass the cap, so leave headroom.
Output formats and exit codes
There are three. text is the default and prints plain text. json returns one structured object carrying the result, the session ID and metadata. stream-json returns newline-delimited JSON, one event per line, for a consumer that wants to watch the run happen rather than wait for it.
With json, the text of the answer sits in the result field, and the payload also carries total_cost_usd and a per-model cost breakdown. Both of those are client-side estimates and can differ from the actual bill. Adding --json-schema puts schema-conforming output in structured_output instead, and an invalid schema stops the run with an error naming the validator's complaint. A run that ends without valid structured output has no structured_output field at all, which is why Anthropic's own guidance is to check that the field is present before a script uses it.
For stream-json, the documented way to receive tokens as they are generated is to pair it with --verbose and --include-partial-messages, and --include-partial-messages requires both --print and the stream format. The last line of the stream is a result message carrying the final response text, the cost and the session metadata. One detail worth knowing if your consumer is slow: Claude Code waits for queued output to drain before it exits, scaling that wait with how much is still queued and capping it at 30 seconds.
| Field or signal | What it holds |
|---|---|
result | The answer as text on the success arm, and where a text-mode failure is printed. The JSON error arm carries errors instead, with no result at all |
subtype | success, or one of error_max_turns, error_during_execution, error_max_budget_usd and error_max_structured_output_retries |
is_error | A boolean on both arms of the result, which is why it is worth reading separately from the subtype |
errors | An array of strings, present on the error arm |
permission_denials | The calls the run was denied, listed on the final result message |
total_cost_usd | An estimate, not a bill |
| The process exit code | 0 on success and non-zero when the run fails, as documented. The section on silent exits covers the reported cases where it does not tell you what happened |
One process-level fact is worth keeping next to the exit code. A -p run stopped with SIGTERM, from kill or from a process supervisor, exits with code 143, leaves the turn in progress unfinished, and records no result for it. If you want the turn ended rather than abandoned, send SIGINT, or the Agent SDK's interrupt(), before you stop the process.
Permissions in scripts and CI
This is the part that decides whether the job works at all, and it starts with one sentence of Anthropic's. In a Manual-mode print-mode run, a call that needs approval is denied unless something approves it, such as a PermissionRequest hook or --permission-prompt-tool. There is no screen to ask on, so a missing rule reads as a refused tool rather than as a prompt nobody answered.
| Approach | What it allows, and what it costs you |
|---|---|
--allowedTools | The tools you list run without asking. Narrowest option, and the one to start with |
--permission-mode dontAsk | Denies every call that would otherwise prompt. Anthropic suggests it for locked-down CI, and a mod you installed can still approve some calls |
--permission-mode acceptEdits | Writes files without prompting and auto-approves common filesystem commands, while other shell commands and network requests still need a rule |
--permission-mode auto | A classifier reviews most actions, so you list nothing beforehand |
--permission-prompt-tool | An MCP tool of yours answers each request. Powerful, and it makes the run wait for that server to connect before the first turn |
--permission-prompts none | For when a host exists but nobody is behind it: the run neither consults nor waits on it |
That last flag is the one people miss. It matters most when the run has a permission host, either an Agent SDK app with a canUseTool callback or an MCP tool passed with --permission-prompt-tool, because without it the run waits for that host to answer every request. With it, the run does not consult or wait on the host, anything that would prompt is denied unless a hook allows it, Claude is told nobody can approve the request and not to retry it, and the tools that need a person's answer are removed from the session. It needs Claude Code 2.1.259 or later, and earlier versions reject it as an unknown option.
Then there is trust, which is the quietest failure in the set. A print-mode run never shows the workspace trust dialog, and in a folder that was never trusted it does not use the project's permissions.allow rules at all. It prints a warning to stderr saying the workspace has not been trusted, while servers in .mcp.json connect without asking, approved or not. If you provision allow rules into a fresh work directory and they seem to do nothing, that is the reason. The trust can be written by hand as projects["<path>"].hasTrustDialogAccepted set to true in ~/.claude.json, and Claude Code prints that exact key in the warning it emits. Before running a print-mode job in a repository you did not write, the docs name four ways to bound it: --setting-sources user, --bare, --settings '{"disableAllHooks": true}', and a disabledMcpjsonServers entry.
Why it asks for permission, or waits on one
Two different failures look the same from outside, and the first thing worth doing is working out which one you have. Since Claude Code 2.1.302, a claude -p or Agent SDK run with no permission mode set starts in Manual mode, which appears as default in SDK options and settings. In that mode a call needing approval is denied, not prompted. So a job that did half its work and stopped usually never asked anybody anything, and the thing to look for is a denial.
The contrast with a terminal is the reason this surprises people. The built-in default table gives a terminal session on 2.1.283 or later auto, and gives claude -p and the Agent SDK default. The same command in the same folder behaves differently depending on whether a human is watching, which is a sensible default and a confusing one to debug.
The second failure is a run that waits, and goes on waiting. That happens when a permission host exists and is not answering, most often an MCP permission tool whose server is slow, or an SDK host that has stopped reading. --permission-prompts none is the documented answer when nobody is available: the run stops consulting the host, and anything unresolvable is denied instead of waiting.
Reporters have hit the edges of all this. Two open reports are worth knowing before you spend an afternoon on your own configuration. In #103865 a session launched with --permission-mode bypassPermissions and --permission-prompts none still stopped on a prompt nothing could answer, on calls already listed in permissions.allow, and stayed stopped until a watchdog killed it. In #79707 a print-mode run in a fresh runner directory silently ignored the allow rules that had been provisioned there, which is the same rule seen from the failure side: the folder had never been trusted, so the rules were not read.
The rest are about what a denial does not tell you, and about a flag that is not a denial at all. #76867 reports that with --permission-mode dontAsk, a run whose required tool was denied still finished with exit code 0, is_error false and a success subtype, leaving the only signal inside the result text. #98395 reports a transient classifier failure in auto mode stopping a headless run with no retry or fallback. And #52506 is the reason dontAsk turns up in every one of these threads: its reporter found --dangerously-skip-permissions and --permission-mode bypassPermissions both still showing a one-time confirmation dialog and parking there for a keypress, while dontAsk proceeded without one.
Why a headless run hangs
The documented waits come first, because they are the ones you can switch off. A print-mode run can stay open after its final turn while it waits for background work Claude started: background commands, background subagents and workflows, Monitor watches, and pending self-paced /loop wakeups. By default, with no background command from the main conversation still running, Claude Code stops whatever is left after ten minutes of continuous idle waiting and drops its partial result. A scheduled wakeup is the exception: the run waits for each one to fire and runs its iteration until the loop ends, even past that ten minutes. CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS moves that ceiling, defaults to 600000, starts over each time Claude takes a turn to handle a background result, and set to 0 waits indefinitely.
You are not left guessing which one you have. When stderr is a terminal and the run has waited five seconds, Claude Code prints a line to stderr beginning Waiting for background work to finish and naming the work. With json or stream-json output that line is printed only when stdout is not a terminal, so the JSON a script reads never carries it, which is the right trade and also the reason a CI log can look silent while the run is waiting.
Startup is the second place time goes. When you pass --mcp-config with -p, Claude Code waits for servers that are still connecting before the first turn, up to MCP_TIMEOUT, 30 seconds by default, and --permission-prompt-tool adds the same wait for that tool's server specifically. CLAUDE_CODE_MCP_STARTUP_WAIT_MS, added in 2.1.274, bounds that first-turn wait and accepts 0 to skip it. A host-side MCP server that stopped responding mid-handshake could stall the first message outright until 2.1.281 fixed it, so a very old build is worth ruling out. The wait at exit runs the other way for MCP: a print-mode run does not wait for MCP work in the background, so a call that automatic backgrounding moved loses its result, and a remote MCP task has to be followed by resuming the session rather than by a second one-shot run.
Three reported hangs are not explained by any of those. #91987 describes a headless job hanging at startup with no output and no timeout of its own while an interactive session held a version-directory lock on the same machine. #86074 reports no timeout at all on the API request itself, so a silently dropped connection left the run waiting for hours without timing out, retrying or erroring. And #73373 reports resuming a session with -r while piping the turn through stdin hanging with zero output, where passing the same turn as an argument worked.
Two versions are worth checking before you blame your own script. In 2.1.288 the background command time limit was narrowed to unattended sessions, and print-mode runs were fixed for occasionally ignoring SIGTERM when a supervisor sent SIGCONT alongside it. In 2.1.304 a plugin that kept a headless run going past --max-turns, Stop or ended input, by spawning subagents or re-running a finished one, was fixed. Both are the kind of thing that looks like a hang and is a version.
The practical rule that comes out of all this is to give the job its own ceiling. A supervisor timeout is the only thing that bounds every wait, documented or reported. --max-turns bounds the turns and the background-wait variable bounds one of the waits; the rest have to be bounded from outside.
Why a run exits with no output
Start with the run that never started. Bare claude needs stdout to be a terminal to open the interactive interface, so when stdout is redirected it runs non-interactively instead, which is the same mode as -p. With no prompt on the argument and nothing piped on stdin, that produces Error: Input must be provided either through stdin or as a prompt argument when using --print. A prompt made only of whitespace fails the same way with Error: Input contained only whitespace, and the fix in both cases is to check that whatever builds your prompt produced text before you call Claude Code.
The next cause is subtler, and it is documented rather than reported. Claude Code prints a failure inside the run as the result on stdout. Missing authentication is the example Anthropic gives. So a job that throws stdout away and only looks at the process status can lose the very sentence that says what went wrong.
Past that, three reports, two of them open and one closed as not planned, describe runs that finished looking successful when they had not. #79500 found an API failure inside a print-mode run returning subtype success with is_error true in the same object, printing the error to stdout rather than stderr, and exiting 0. #76867 is the denied-tool version of the same thing, and it was closed as not planned. #94392 reports a run exiting with its own background subagents still in flight, reporting each of them stopped, and still ending with a success subtype and exit code 0.
| Symptom | What to check |
|---|---|
| Nothing at all on stdout | Whether the run started. No prompt on the argument or on stdin stops it before it begins |
| An error message treated as the answer | The result text itself. A failure inside the run is printed there, on stdout |
| Exit 0 and the work is not done | permission_denials, and is_error separately from subtype |
| A clean exit with subagents missing | Whether the run spent its background wait. CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS sets how long it will |
| Exit 143 | A SIGTERM from a supervisor or a timeout. The turn was abandoned, not finished |
| A very large JSON payload with no newline | A stream-json producer that is not terminating its messages. Over 268,435,456 characters with no newline the run exits 1 by design |
Two quieter cases produce an answer that looks wrong rather than missing. If Claude Code cannot read stdin at all, because the process that started it closed its end, it prints a warning to stderr and carries on with the prompt from the command line, so the run you thought was fed by a pipe used the argument instead. And if the working directory is deleted mid-session the session keeps running; a turn that starts while the directory is missing emits a warning in stream-json output, and shell commands fail until it exists again.
If you want the job to fail loudly, the field to key on is permission_denials on the final result message, which with stream-json output also appears as permission_denied system messages as they happen. A feature request, #90578, asks Anthropic to surface a denial at the process level so a script could branch on it without reading the envelope at all. Until something like that lands, reading the envelope is the reliable way.
Where this page stops
Three subjects sit next to this one and belong to other pages. A full account of the permission modes, and of which one each kind of session starts in, is its own subject; this page names only the modes an unattended run has to choose between. The catalogue of Claude Code's error text, message by message, lives on the error messages page, which is where to go when you have a string and want to know what it means.
The third is the boundary around what an unattended run may touch on the machine. This page covers the flags that decide what Claude Code will ask about; the sandbox page covers the operating-system side of the same question, which is a different layer and worth reading before you point a print-mode job at a repository you do not own.
What people have reported
These are the reports this page leans on, each read on October 25, 2026, with the login of the person who filed it, the day they filed it and its state on that day. They are reports, which is a different thing from documented behaviour, and they are labelled as reports everywhere they appear above.
| What was reported | Filed by | Date |
|---|---|---|
| No non-interactive way to trust a workspace: allow rules provisioned into a fresh runner folder were silently ignored. Open | leeguooooo | July 21, 2026 |
dontAsk denial reports success: exit 0, is_error false, a success subtype, and the work not done. Closed as not planned | NongKnot | July 12, 2026 |
| An API error returns a success subtype and exit code 0, with the error text only in the result. Open | chukxoka4 | July 20, 2026 |
| Resume with piped stdin hangs, while the same turn passed as an argument worked. Closed as not planned | discorder256 | July 2, 2026 |
| Hangs at startup on a contended version lock, with no output and no timeout of its own. Open | dbgit2000 | September 4, 2026 |
| No API request timeout: a dropped connection left the run waiting for hours. Closed as not planned | networkingguru | August 12, 2026 |
| An empty classifier verdict stops a headless run, with no retry or fallback. Open | RoboNater | September 30, 2026 |
| Exits with its own subagents still running and still reports a success subtype and exit 0. Open | pghaneian-wizard | September 14, 2026 |
| A bypassPermissions session still hangs on an unanswerable prompt. Open | nicogreenarry | October 20, 2026 |
A request that a denial show up at the process level, not only in permission_denials. Open | PengonQ | August 29, 2026 |
| skip-permissions still shows a confirmation dialog and parks there, while dontAsk does not. Closed as a duplicate | ph47s74x | April 23, 2026 |
A closed report is not the same thing as a fixed one, which is why each row carries its state. The dontAsk report above was closed as not planned, so closing it shipped no fix; the skip-permissions report was closed as a duplicate, which says the report was already known rather than that the dialog went away.
Where Omniscio fits
Plainly: print mode is Claude Code's own, it is a flag on a command you run yourself, and nothing on this page needs Omniscio. Where Omniscio sits is one level up. It is a desktop app for running many Claude Code agents side by side, built around one calm inbox rather than a wall of terminals, and its own line is “Any model, any harness — Claude Code, Codex, OpenCode, Devin, Pi, and Hermes all supported.”
It also runs Claude Code itself in this mode. Its spawn argument list is -p, --output-format stream-json, --input-format stream-json, --verbose, --permission-prompt-tool stdio, --disallowedTools AskUserQuestion,EnterWorktree,ExitWorktree, one --append-system-prompt value and --include-partial-messages, with --model and --resume added when a model or a session to resume is set. That makes every Omniscio session running Claude Code an example of this page's subject rather than a separate thing, and it is also why the permission-host section above is worth reading even if you never open a terminal: that stdio value is the control channel Omniscio’s own harness answers on, which our source names as the reason the flag is required.
Two defaults are worth knowing before you compare a bill or a plan. Omniscio's per-session spend cap applies to sessions on a metered model key or on Omniscio credits, and never limits a subscription session. And the Free plan covers 8 projects and 10 agents at once, with Pro at $20/month or $192/year. The features page lists what the app does today and the pricing page has the plans.
One more thing that matters once a job stops being a single script. Running many unattended sessions in one repository at once is where isolation starts to matter, and in Omniscio worktree isolation is off until you turn it on, per session, per project or app-wide. The exception is an engine whose tools run outside Claude Code's guardrail hooks, which runs in its own copy by default in a git folder. The guide to running many coding agents covers the rest.
How this page was checked
Every statement here about Claude Code comes from Anthropic's own documentation, read in its Markdown form on October 25, 2026, from the pages listed below. The version line comes from Anthropic's own changelog: the newest release listed is 2.1.310, dated October 23, 2026, and each version named on this page is the one the changelog or the documentation attaches to that change. No print-mode run was executed on this machine to produce this page, so the behaviour described is Anthropic's account of it, and where a reporter describes something different, the page says which is which.
The reports were read the same day through GitHub's API, in Anthropic's anthropics/claude-code repository, and each carries the login of the person who filed it, the day they filed it and its state on that day. We quote what the report says, not what the product does, which is why an open one is labelled open and a closed one carries how it was closed.
What this page says about Omniscio comes from our own product pages and our own source. If Anthropic's page and this one ever disagree about Claude Code, Anthropic's page is right, because this part of Claude Code changes on nearly every release.
Sources
Anthropic's Claude Code documentation, read October 25, 2026, at Claude Code 2.1.310:
- Run Claude Code programmatically: the print flag, bare mode, background work at exit, SIGTERM, the output formats, structured output, the tool and permission flags, and the permission host
- CLI reference: the flag table, including
--permission-prompts,--permission-prompt-tool,--max-turnsand--max-budget-usd - Permission modes: the built-in default table, and the sentence that print-mode runs start in Manual from 2.1.302
- Configure permissions: what runs before you trust a folder, and the four ways to bound a print-mode run in a repository you did not write
- Error reference: the two input errors, the large stream-json input error, and the interactive case that runs print mode by accident
- Environment variables:
CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS,MCP_TIMEOUTandCLAUDE_CODE_MCP_STARTUP_WAIT_MS - Agent SDK, TypeScript reference: the result message, its two arms, the subtype values and the fields each carries
- The changelog: release 2.1.310, the newest listed, and 2.1.274, 2.1.281, 2.1.288 and 2.1.304 where this page names a version
- Anthropic's Claude Code issue tracker: the reports above, read through GitHub's API on the same day
Omniscio's own pages and source, for what this section says about this app:
- Omniscio's homepage: the product description, which carries the harness line
- Features: the spend-cap rule and its subscription exemption, and what the app does today
- Pricing: the free plan and the Pro price
src/main/process/spawn-build.tsin our own source: the spawn argument list this page quotes- Session isolation in the library: worktree isolation off by default, and the auto-approving engines that differ
- Running many coding agents: where one script becomes a fleet
Common questions
What does headless mode mean in Claude Code?
It is print mode: the -p or --print flag runs Claude Code once and returns the answer, instead of opening the interface you would type into. A script calls it the way it calls any other command-line tool, which is why it is the mode a CI job uses.
Why does claude -p exit with no output?
The first thing to rule out is that the run never started, which happens when no prompt arrives on the argument or on stdin. Past that, the documented rule is that a failure inside the run is printed as the result on stdout, so check the result text and the exit code together rather than trusting either alone.
Why does a headless Claude Code run hang?
Four waits are documented: a run that stays open for background work after the final turn, a run waiting on a permission host to answer, an MCP server still connecting at startup, and a slow consumer of a stream-json pipe. From five seconds in, a run waiting on background work says so on stderr.
Why does claude -p ask for permission when nobody is there?
From Claude Code 2.1.302, a print-mode run with no permission mode set starts in Manual, and a call that needs approval is denied rather than prompted. So the usual cause is a missing allow rule, not a prompt waiting on a screen that is not there.
Does claude -p work with a Pro or Max subscription?
It does unless you run it in bare mode. Bare mode never reads OAuth credentials or the system keychain, which is exactly why Anthropic says it does not use your subscription login and needs an API key in the environment or an apiKeyHelper supplied through the settings flag.
How do I make a CI job fail when Claude was denied a tool?
Read the permission_denials array on the final result message, which with stream-json output also appears as permission_denied system messages. A denied tool has been reported arriving with exit code 0 and a success subtype, so the exit code alone will not catch it.
Run those print-mode sessions from one window
Install Omniscio, keep your Claude Code sessions in one list, and see what each one needs without reading a terminal. The features page lists what is in it today.