
Claude Code --bare: Cutting claude -p Token Overhead
claude --bare -p is the leanest way to run headless Claude Code, but it only authenticates with ANTHROPIC_API_KEY, so it won't touch your Max or Pro subscription. If your scripts run on the subscription, you get most of the savings with three flags instead: --strict-mcp-config, --tools "" and --setting-sources "". I measured it today on Claude Code 2.1.296. A call that only had to say "OK" billed 32,207 input tokens by default and 3,455 with those flags.
Why Is a One-Word claude -p Call 32,000 Tokens?
Because claude -pboots the same agent you get interactively. It sends the full Claude Code system prompt, a definition for every tool it could use, every MCP server you've ever connected, and a stack of system reminders: your skills list, CLAUDE.md, auto memory and whatever your SessionStart hooks print. Your prompt is a rounding error on top.
I run a lot of scripted claude -pcalls: cron jobs, a local proxy that turns chat turns into headless runs, small classifiers that answer one question and exit. Most of them don't need a single tool. So I wanted to know what each one was really paying before it did anything useful.
The test is boring on purpose. Prompt: "Reply with the single word OK." Model: claude-haiku-5-5. Output: --output-format json, which carries a usage block and a total_cost_usd figure. I ran every configuration three times from a clean environment (no inherited CLAUDE_* variables) and added input, cache-write and cache-read tokens together. All three runs matched to the token.
claude -p "Reply with the single word OK." --output-format json \
| python3 -c 'import json,sys; u=json.load(sys.stdin)["usage"]; \
print(u["input_tokens"]+u["cache_creation_input_tokens"]+u["cache_read_input_tokens"])'Takeaway: the overhead is fixed per call, so it matters most for exactly the small, frequent jobs you'd think are cheap.
What Does Each Flag Remove?
These are real billed numbers on my Max subscription. Each row adds one flag to the row above it. Cost is Claude Code's own total_cost_usd for a warm call, where almost everything is a cache read.
| Configuration | Input tokens | Warm cost | What changed |
|---|---|---|---|
claude -p (home directory) | 32,207 | $0.00032 | 34 tools offered, plugins + hooks + memory |
claude -p (fresh empty directory) | 41,807 | $0.00044 | claude.ai connectors loaded: 309 tools offered |
+ --strict-mcp-config, empty --mcp-config | 30,947 | $0.00031 | MCP gone, built-in tools still there |
+ --tools "" | 8,141 | $0.000084 | no tool definitions at all |
+ --setting-sources "" | 3,455 | $0.000037 | no CLAUDE.md, memory or settings hooks |
claude --bare -p (subscription only) | 0 | $0 | Not logged in · Please run /login |
Two things surprised me. First, the fresh empty directory cost morethan my home directory. In that directory Claude Code pulled in my claude.ai connectors (Gmail, Drive, Figma, Shopify and friends) and offered 309 tools instead of 34. My read is that tool search deferred most of those definitions, which is why the call only grew by 9,600 tokens. But it grew. An "empty" folder isn't a clean room.
Second, --strict-mcp-configon its own barely moved anything: 41,807 down to 30,947, and that's still 30K. The big drop came from --tools "". Built-in tool definitions (Bash, Edit, Agent, the cron and worktree tools, and so on) were the single largest chunk. I covered the MCP side of this in what MCP servers really cost per turn, but for headless calls the built-ins are the bigger bill.
The last flag, --setting-sources "", took the remaining 4,700 tokens. That was my user-level CLAUDE.md, auto memory and hook output being injected as reminders. Useful in an interactive session. Pure waste in a classifier.
Takeaway: --tools "" is the flag that matters. Combined with the other two, the call went from 32,207 to 3,455 input tokens, about 9x less.
Does claude --bare Work With a Claude Max Subscription?
No. This is the part that will catch people. The Claude Code headless docs say bare mode "never reads OAuth credentials or the system keychain." It wants ANTHROPIC_API_KEY or an apiKeyHelper in --settings. With only my subscription login on the machine, this is what I got:
$ claude --bare -p "Reply with the single word OK." --output-format json
{"is_error":true,"result":"Not logged in · Please run /login", "duration_ms":33, ...}33 milliseconds, no API call, no tokens. Fine if you bill the API anyway. A problem if your scripts run on the subscription login. One way out: if you've claimed the monthly API credits that come with Max, an API key from that linked Console org runs --bare against those credits instead of a new bill.
The same docs page also says --bare"will become the default for -p in a future release." Read that next to the auth rule. When it flips, every claude -pscript that relies on a subscription login will start failing with "Not logged in" unless Anthropic changes the auth story first. I'd rather not find out from a dead cron job at 3 a.m.
Takeaway: if you run headless Claude Code on a subscription, write the explicit flags into your scripts now. Don't depend on whatever -p loads by default.
How Much Smaller Is a --bare Request?
I couldn't bill --bare on the subscription, so I measured the request itself. I pointed ANTHROPIC_BASE_URLat a tiny local Python server that logs each request body and streams back a canned "OK". Same prompt, same model, API-key auth so bare mode would run. Disclosure: these are payload sizes, not billed tokens.
| Configuration | Request body | System prompt | Tools sent |
|---|---|---|---|
claude -p (default) | 130 KB | 8,607 chars | 32 |
Three flags (no --bare) | 10.2 KB | 8,477 chars | 0 |
--bare | 4.35 KB | 206 chars | 3 (Bash, Edit, Read) |
--bare --tools "" | 1.0 KB | 206 chars | 0 |
Bare mode doesn't just skip the extras. It swaps the 8,600-character Claude Code system prompt for a 206-character one and ships three tools. My three-flag call still carries the full system prompt, which is why it bottoms out around 3,500 tokens. Using the three-flag call's ratio (10.2 KB billed as 3,455 tokens), a bare call lands somewhere near 1,500 tokens, and --bare --tools ""near 350. That's an estimate, not a bill.
Startup got faster too. Against the local stub, where the network costs nothing, the default call took 0.86 to 1.72 seconds end to end. The three-flag call took 0.50 to 0.69. Bare took 0.44 to 0.53. Half a second per call won't matter in a nightly job. In an n8n loop over 500 rows it does.
Takeaway: on an API key, use --bare. On a subscription, the three flags get you about 9x of the way there.
The Lean claude -p Call I Use on a Subscription
Put this in one wrapper script and make every scheduled job call the wrapper:
claude -p "$PROMPT" \
--model claude-haiku-5-5 \
--output-format json \
--no-session-persistence \
--strict-mcp-config --mcp-config '{"mcpServers":{}}' \
--tools "" \
--setting-sources ""--no-session-persistence keeps a thousand throwaway sessions out of your resume list. If the job does need a tool, name it instead of passing an empty string: --tools "Read" costs far less than the full default set. And remember what --setting-sources "" also turns off: hooks from your settings files. If you built guardrails with hooks, like the ones in my Claude Code hooks in production post, they won't run here. Pass what you need with --settings.
Structured output survives the diet. With every built-in tool removed, this call still returned valid JSON in structured_output:
claude -p "Classify this lead: 'My AC died last night, can someone come today?'" \
--strict-mcp-config --mcp-config '{"mcpServers":{}}' --tools "" --setting-sources "" \
--output-format json \
--json-schema '{"type":"object","properties":{
"intent":{"type":"string","enum":["emergency_service","quote","support","spam"]},
"urgency":{"type":"integer","minimum":1,"maximum":5}},
"required":["intent","urgency"],"additionalProperties":false}'Result: {intent: "emergency_service", urgency: 4}, two turns, 3,733 input tokens. If you're choosing between schema enforcement and tool calls for this kind of job, structured outputs vs tool use walks through the tradeoff.
When Should You Keep the Default claude -p?
When the job is actually agentic. If the headless run has to read a repo, run tests, edit files and follow your project's CLAUDE.md, those 30K tokens are the context it needs. Stripping them saves money and makes the agent dumber. Same logic as subagent token cost: pay for context when the task uses it, not by default.
- Classify, summarize, extract, rewrite: three flags, or
--bareon an API key. - Read a few files and answer: three flags plus
--tools "Read"and--add-dir. - Fix a failing test in a real repo: default
-p, from the repo directory, with--allowedToolsset deliberately.
One more warning from the docs worth repeating: without --bare, a -prun executes the hooks in a project's .claude/settings.json and connects its .mcp.json servers, with no trust prompt. Running default claude -p inside a repo you just cloned is a bigger risk than the token bill.
claude -p and --bare FAQ
Does claude --bare work with a Claude Max subscription?
No. Bare mode never reads OAuth credentials or the keychain. On Claude Code 2.1.296 with only a Max login, claude --bare -p exited in 33 ms with 'Not logged in · Please run /login'. It needs ANTHROPIC_API_KEY or an apiKeyHelper, which bills the API, not the subscription.
How many tokens does a claude -p call use before my prompt?
In my test a one-word reply billed 32,207 input tokens from my home directory and 41,807 from a fresh directory where claude.ai connectors loaded. The overhead is the system prompt, every tool definition, and system reminders like skills, CLAUDE.md and memory. Check yours with --output-format json.
How do I make claude -p cheaper without --bare?
Pass --strict-mcp-config --mcp-config '{"mcpServers":{}}' --tools "" --setting-sources "". That combination took my call from 32,207 to 3,455 input tokens on the subscription, and the reported warm cost from $0.00032 to $0.000037 per call.
Will --bare become the default for claude -p?
Claude Code's headless docs say --bare is the recommended mode for scripted and SDK calls and will become the default for -p in a future release. If your scripts rely on a subscription login, pass the explicit flags now so the switch doesn't break them overnight.
Does --json-schema still work with --tools ""?
Yes. With all built-in tools removed, a lead-classification call with --json-schema returned structured_output {intent: emergency_service, urgency: 4} in two turns and 3,733 input tokens.
Running Claude Code From Cron or n8n?
I build headless Claude agents that run on schedules, webhooks and queues, and I measure what each call costs before it ships. Send me your setup and I'll tell you where the tokens are going.
Related Posts
AI Agents
Claude Mid-Conversation Tool Changes: Keep the Cache
Editing the tools array invalidates the prompt cache for the whole conversation. tool_addition and tool_removal change what Claude can call without ever touching it — plus the placement rules that 400.
AI Agents
Tool Search vs Programmatic Tool Calling in Claude
One cuts tool-definition tokens, the other cuts tool-result tokens. The decision rule, the 400s that surprise people, and the MCP restriction that makes the choice for you.
AI Agents
MCP Server Security: How to Stop Tool Poisoning
Tool poisoning is when an MCP server hides instructions inside its own tool descriptions — text your agent reads as commands and you almost never see. The model obeys it because, inside the context window, a description and a system prompt are the same kind of thing, which is why no system prompt fixes this. The four controls that hold are structural: approve individual tools instead of whole servers, pin exact versions and diff the tool descriptions in CI so a rug pull is a failed build, keep secrets out of the model's context entirely, and run local servers in a container with no network access.