We Asked Claude Code to Say Pong and It Sent 44,137 Tokens
A three-token headless prompt sent 44,137 tokens of preamble and cost 31 cents. The identical command four seconds later cost 2.2 cents.
The WJS Desk
Sep 1, 2026 · updated 2 hours ago · 8 min read

We asked Claude Code, in headless mode, to reply with the word "pong." It did. The JSON receipt said the call sent 44,137 prompt tokens and cost 31 cents.
Then we ran the identical command again and it cost 2.2 cents. Then again, and it cost 26. Same prompt, same directory, same model, a twelvefold swing between consecutive runs.
This is a tutorial about scripting Claude Code from a shell, and about the two things nobody tells you before you put it in a loop: what a trivial invocation actually costs, and why that number moves.
What you will end up with
A working headless harness that returns structured JSON you can branch on, a measurement of your own baseline prompt size, and an understanding of the caching behaviour that decides your bill. About 25 minutes. Every call in here costs real money, so read the cost section before running the loop.
The basics, and the flag that saves your sanity
Headless mode is --print, or -p. It runs one turn and exits:
claude --print "Summarise the changes in this repo in one line."
Add < /dev/null to every invocation. Without it the CLI waits three seconds for stdin that is never coming and prints:
Warning: no stdin data received in 3s, proceeding without it.
Three seconds is nothing once. In a loop of forty test calls it is two minutes of waiting and forty lines of noise burying your actual output. This was the first thing we got wrong.
Ask for JSON, because the metadata is the point
--output-format json turns the reply into a structured result, and the interesting part is not the text:
claude --print --output-format json "Reply with exactly: pong" < /dev/null
The fields worth knowing:
| Field | What it gives you |
|---|---|
result | The reply text |
is_error | Boolean, the thing to branch on |
total_cost_usd | What that call cost |
usage | Input, output, cache creation and cache read tokens |
permission_denials | Array of what your rules blocked |
num_turns | Turns taken, for runaway detection |
stop_reason | end_turn, or why it stopped |
session_id | For --resume |
duration_ms and ttft_ms | Total time and time to first token |
permission_denials deserves a paragraph of its own. It is a machine-readable list of everything your permission rules stopped, which turns "did my deny rule work" from a question you answer by reading refusal prose into one you answer with jq. We used exactly this to test permission rules, and it is the single most useful field in the object.
claude --print --output-format json "$PROMPT" < /dev/null \
| jq -e '.is_error == false and (.permission_denials | length) == 0'
What a trivial call actually costs
Here is the receipt for "reply with the word pong," a prompt of three tokens producing five:
input_tokens 3
cache_creation_input_tokens 30,183
cache_read_input_tokens 13,951
output_tokens 5
total_cost_usd 0.309
44,137 tokens of prompt for a three-token question. None of that is yours. It is the system prompt, your tool definitions, your CLAUDE.md, your rules, and your skill listing, assembled before your text is appended.
We isolated how much of it is project configuration by running the same call in two directories:
| Working directory | Prompt tokens |
|---|---|
| Scratch directory, no project CLAUDE.md | 44,137 |
| Our repo: 12KB CLAUDE.md, six skills | 48,163 |
| Difference attributable to project config | 4,026 |
So our project's CLAUDE.md and skills cost about 4,000 tokens on every invocation, and the 44,137 baseline underneath it is everything that loads before a project is even involved.
This is what makes headless expensive. An interactive session pays this once and amortises it over an afternoon. A script that shells out per file pays it every time. A hundred-file loop is a hundred times 44,000 tokens of preamble to ask a hundred small questions.
The cache lottery
Three consecutive runs of the same command:
| Run | Cache created | Cache read | Cost |
|---|---|---|---|
| 1 | 30,183 | 13,951 | $0.3090 |
| 2 | 0 | 44,161 | $0.0222 |
| 3 | 25,370 | 18,020 | $0.2628 |
A full cache hit was fourteen times cheaper than a cache miss for the identical prompt. Nothing in the command changed between run 2 and run 3; the cache simply was not fully warm any more.
The same command cost 2.2 cents and 26 cents four seconds apart, and nothing you control decided which.
The practical consequence is that you cannot cost a headless workflow from one sample. Run it ten times and take the distribution, because a benchmark that happened to land on a warm cache will understate your bill by an order of magnitude. It also argues for batching, which we tested rather than assumed. See below.
We tested the batching advice instead of assuming it
"Batch your questions" is the sort of thing that sounds obviously right and is worth checking anyway. So we asked three trivial arithmetic questions two ways: three separate invocations, then one invocation asking all three.
| Approach | Prompt tokens | Cost |
|---|---|---|
| Call 1 of 3 | 45,740 | $0.2863 |
| Call 2 of 3 | 45,740 | $0.2863 |
| Call 3 of 3 | 44,969 | $0.2786 |
| Three separate calls | 136,449 | $0.8512 |
| One batched call | 45,761 | $0.2866 |
2.97 times cheaper for the same three answers, and the ratio is not a coincidence: three questions, three preambles, three times the price. The batched call cost 21 tokens more than a single separate one, which is what asking three questions instead of one actually costs.
The detail that matters more than the headline is the middle column. All three separate calls paid roughly the same, around 28 cents. Running the same preamble back to back did not reliably earn a cache discount. So the "just run it in a loop, the cache will handle it" assumption fails twice over: the cache may not be warm, and when it is not, you pay full price for every iteration.
The rule that falls out of this: the number of invocations is your cost, near enough. Not prompt length, not output length, not model choice at this scale. If you can restructure a loop of N calls into one call handling N items, you have cut the bill by close to N.
What broke
Our first cost measurement was wrong by 14x and we did not know it, because we ran the command once, wrote down 31 cents, and only re-ran it out of habit. One sample of a cached system is not a measurement.
We assumed --print was cheap because it is short. The length of your prompt is close to irrelevant next to a fixed 44,000-token preamble. A one-word question and a three-paragraph question cost nearly the same, which inverts the instinct that terse prompts save money. Terse prompts save nothing. Fewer invocations save a lot.
The stdin warning cost us two minutes and a lot of scrollback before we found < /dev/null.
Exit codes, and the trap in them
If you are putting this in a script you need to know how it fails, so we broke it deliberately.
| Condition | Exit code | Where the message goes |
|---|---|---|
| Normal success | 0 | Reply on stdout |
Invalid --output-format | 1 | stderr, clean argument error |
Unrecognised --model | 1 | Prose on stdout, detail on stderr |
That last row is the trap. Pass a model name that does not exist and the exit code is correctly 1, but stdout still receives a fluent English sentence:
There's an issue with the selected model (definitely-not-a-model).
It may not exist or you may not have access to it.
A script doing ANSWER=$(claude --print "$Q") and moving on has just captured an error message as its answer. It reads like a response, it is not empty, and nothing about it looks wrong until it reaches whatever consumes it. The exit code is the only thing that distinguishes it.
So the pattern to write is the boring one, always:
if ! OUT=$(claude --print --output-format json "$Q" < /dev/null); then
echo "claude failed" >&2; exit 1
fi
echo "$OUT" | jq -e '.is_error == false' >/dev/null || { echo "error result" >&2; exit 1; }
ANSWER=$(echo "$OUT" | jq -r '.result')
Check the exit code, then check is_error, then take .result. Two guards rather than one, because they catch different failures: the exit code catches the CLI refusing to run, and is_error catches a run that completed badly.
Common mistakes
- Looping per file. The most natural shape for a shell script is the most expensive one available. Batch the question.
- Parsing prose instead of JSON. The text reply is written for a human and its wording changes.
is_errorandpermission_denialsdo not. - Capturing stdout without checking the exit code. A bad model name exits 1 and still writes a plausible sentence to stdout. Your script will happily use it.
- Forgetting the working directory matters. The same script costs more in a repo with a large CLAUDE.md. Ours added 4,026 tokens per call.
- Costing from one run. See above. Ten runs, then decide.
- Omitting
< /dev/null. Three seconds and a warning on every call. - Ignoring
num_turns. A headless call that quietly took twelve turns is a bug you want to catch in a script, not on an invoice.
What we would not do yet
We are not putting a per-file headless loop into CI. At roughly 44,000 preamble tokens per invocation and a cache that is warm only sometimes, the cost is both high and unpredictable, which is the worst combination for something that runs on every push. Batching first, measuring second, and only then automating.
We are also not trimming our CLAUDE.md purely to save headless tokens. Four thousand tokens per call is real, but that file exists to stop Claude making the same mistake twice, and a wrong answer costs more than four thousand tokens. Trim it because it has grown stale, not because it appears on this bill.
The rollback
Nothing here changes state. Headless mode reads your existing configuration and exits, and the only artefact is a session you can reach with --resume <session_id> or ignore entirely. The scratch directories are deletable:
rm -rf /tmp/headless
The number to take away is 44,137 tokens to say "pong." Before you script this, run one call with --output-format json and look at your own usage block. It is the cheapest measurement available and almost nobody takes it.


