[BUG] Headless `claude -p` (entrypoint `sdk-cli`) uses ~1.8× more of the 5-hour window per token than interactive `cli`, same workload

Status Open
Reported on v2.1.282
Maintainer reply None cached
Activity 8 comments · opened Sep 25, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

On a Max subscription, the same workload uses about 1.8× more of the 5-hour window when it runs headless (claude -p --input-format stream-json, recorded as entrypoint: "sdk-cli" in the transcripts) than when it runs in the interactive TUI (entrypoint: "cli"). Token usage is the same in both modes.

The Agent SDK help article (https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan) says claude -p "still draw[s] from your subscription's usage limits", and I could not find anything saying it is weighted differently.

After moving the same work from the TUI to claude -p, we hit the 5h limit several times a day with no change in workload, and we paid for extra usage (rejected + isUsingOverage: true) that the same work in the TUI would not have needed.

What Should Happen?

The same tokens on the same model should use the same share of the 5-hour window, whether they come from claude -p or from the interactive TUI. If headless usage is intentionally weighted differently, it should be documented.

Error Messages/Logs

Controlled A/B test, 2026-09-25 10:27–10:41 UTC, Claude Code 2.1.282, claude-opus-5, effort high

Phase        | Test spend (API-equiv.) | Other spend        | five_hour utilization | $ per 1% of 5h
-------------|-------------------------|--------------------|-----------------------|---------------
headless     | $12.74                  | $0.40 (headless)   | 52% -> 60%            | ~1.6 (1.5-1.9)
interactive  | $12.91                  | $1.12 (headless)   | 60% -> 65%            | ~3.0 (2.4-3.9)

Ranges come from the 1% resolution of the utilization reading; they do not overlap.

Historical (same account, all Opus 5, API-equivalent spend per 1% of each 5h window that peaked above 25%):
2026-09-18..22  cli      2.1.267/2.1.270  3.1-4.1 $ per 1%  (8 windows)
2026-09-23..24  sdk-cli  2.1.267          1.6-1.9 $ per 1%  (5 windows)

Steps to Reproduce

  1. Create a directory with ~250K tokens of plain text split into files small enough for Read (I used 18 files of ~45K characters of source code).
  2. Prepare a fixed prompt list: turn 1 asks Claude to read all files in parallel and reply only "read"; turns 2–41 are short questions about the content answered in one sentence, without tools.
  3. Headless phase: run

claude -p --input-format stream-json --output-format stream-json --verbose --permission-prompt-tool stdio --replay-user-messages --append-system-prompt "<one short paragraph>" --dangerously-skip-permissions --model claude-opus-5
and send the prompts one by one as stream-json user messages, waiting for each result. Record rate_limit_event.rate_limit_info.unifiedWindows.five_hour.utilization.

  1. Interactive phase: run claude --model claude-opus-5 --dangerously-skip-permissions in tmux, send the same prompts with tmux send-keys, detect turn end with a Stop hook, and read rate_limits.five_hour.used_percentage from the statusline JSON.
  2. Before and after each phase, run a tiny probe (claude -p --model claude-haiku-4-5 --output-format stream-json --verbose "reply: ok") to read utilization the same way.
  3. Compute each phase's token cost from the session transcripts (message.usage, deduplicated by message id + request id) at list API prices, and subtract any other activity on the account during the phase (measured from the transcripts of every machine on the account).
  4. Compare the $ per 1% of the 5-hour window: headless comes out ~1.8× more expensive for the same tokens.

Checks already done:

  • Not the auto-mode classifier: both phases used --dangerously-skip-permissions, and 40 of 41 turns made no tool calls.
  • Not a larger harness in headless: the first request of a headless session is slightly smaller than an interactive one (median ~66K vs ~88K input tokens on 2.1.267).
  • Not missing transcript entries: headless result.total_cost_usd matches the transcript totals.
  • Not usage from other machines: measured and subtracted.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.282 (Claude Code) — also observed on 2.1.267

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Non-interactive/CI environment

Additional Information

Related:

I can share the test scripts and the per-turn measurements if that helps.

View original on GitHub ↗

3 Comments

qvarker · 19 days ago

Two follow-ups:

  1. The gap looks stable over time, not like a one-off repricing. In futin/claude-agents-dashboard#143, splitting their data by entrypoint shows each surface flat within noise over time (−1.6% interactive, −4.1% headless), with the headless rate at ~1.8× the interactive one. Their interactive surface is the Desktop app (claude-desktop) and ours is the TUI (cli), so the difference seems to be headless vs any interactive entrypoint.
  1. Checked two other possible causes:
  • Subagent transcripts (<session>/subagents/) were included in our historical numbers, and the A/B test used no subagents.
  • Retries / 529s: there are no retry events in ~50K lines of our headless stream-json output, so unrecorded retries don't explain it either, at least as far as the CLI reports them.
qvarker · 19 days ago

Re-ran the A/B on 2.1.282 through a local logging proxy (ANTHROPIC_BASE_URL), other account activity subtracted: headless 0.69 $ per 1% of the 5h window (0.59–0.82), interactive 1.59 $ (1.19–2.39). Same gap.

No hidden requests. Every headless /v1/messages call the proxy saw is in the transcript (plus the compaction call, which neither mode records). No retries, no 429/529. Interactive actually sends more requests (a max_tokens: 1 quota check and a title request).
Fewer tokens, more window. Headless used fewer tokens of every type and still more of the window.
What differs on the wire: cc_entrypoint=sdk-cli vs cli in the x-anthropic-billing-header system block and the User-Agent, and the redact-thinking-2026-02-12 beta, which only the interactive client sends. Model, effort, thinking, context_management and the other betas are identical.

Z3l20 · 17 days ago

Independent reproduction on a different plan and model: Pro plan, Claude Code 2.1.283, claude-opus-5-5 at --effort low, Linux, 2026-09-27 09:09 to 09:19 UTC.

Both arms ran the same 31 turns. Turn 1 reads six source files of about 48 KB each with Read; turns 2 to 31 ask for a one-word reply without tools. Both used --permission-mode default --setting-sources project, so no user hooks and no auto-mode classifier, and DISABLE_PROMPT_CACHING=1, so every turn sends about 150K uncached input tokens and the meter moves far more than its 1% step. Utilization came from a Haiku probe (claude -p --output-format stream-json --verbose, rate_limit_event.rate_limit_info.unifiedWindows.five_hour.utilization) before and after each arm, two minutes after the arm ended. Cost is from the session transcripts at list price, and for the headless arm it matches result.total_cost_usd. The transcripts of every machine on the account show no other Claude activity during the test.

| Arm | entrypoint in transcript | List-price cost | five_hour | $ per 1% |
|---|---|---|---|---|
| claude -p --input-format stream-json | sdk-cli | $19.74 | 27% to 56% | 0.68 |
| Interactive TUI in tmux | cli | $22.78 | 56% to 73% | 1.34 |

Headless used the 5-hour window about 2.0 times as fast for the same work, 1.8 to 2.2 times within the 1% steps. The headless stream's own readings agree: 35% at turn 10, 45% at turn 20, 55% at turn 31. A later probe two minutes after the last arm still read 73%, so the gap is not meter lag.

A pitfall for anyone reproducing this: an interactive claude started from a shell that has CLAUDE_CODE_ENTRYPOINT=sdk-cli in its environment records sdk-cli too. My first run did that by accident, because the tmux server inherited the variable from an SDK-launched session. With both arms on sdk-cli, they moved the meter by the same amount: $2.51 and $2.73 of cached usage moved it 2 points each. That control is weak at 2 points, but it fits the entrypoint being what is weighted rather than something else the TUI does.

Apps built on the Agent SDK record sdk-cli as well; in my case that is Paseo, where most of my Claude Code use runs.

Showing cached comments. Read the full discussion on GitHub ↗