Quick answer
Claude Code headless is Claude Code run with nobody at the keyboard, and all 247 recorded runs of it exited with code 0, even the one bug-fix run that fixed nothing. The flag that does it is -p, print mode: Claude Code reads a prompt, does the work, prints what happened and exits. A flag is a switch written after the command, such as --model. Unattended means nobody is there to answer a question or press a key. An exit code is the number a program hands back when it ends, and 0 conventionally means it finished without a failure of its own. A run is one fresh claude -p process from start to exit. The 247 runs were recorded earlier, one at a time on one Windows PC, with Claude Code 2.1.284 and a subscription login, one of the two ways Claude Code can pay for a run (the other is an API key, and none was used). 75 were bug fixes. No new run was made for this class. In all 247 the exit code was 0, stderr (where a program writes its errors) was empty and the last line said success. That includes 39 runs in which Claude Code refused a command (it needed approval, and nobody was there to give it), and one of those left the repository unchanged. So exit 0 tells you the process ended, not that the work is done. Here you build the command, read one real run line by line and practise the checks a script needs. One setup says nothing about yours.
Seven things to take
- Start in a folder you control. The run folder is the working directory, the folder the run starts in. The vendor says a
-prun shows no workspace trust dialog (the question of whether you trust the folder) and, without--bare, runs the hooks (scripts) and connects the MCP servers (extra tools) that a project's settings name. All 247 recorded runs used--safe-mode, which the vendor says starts with all customizations disabled. Both are the vendor's descriptions, not tested here. - Give the prompt on stdin. Stdin, stdout and stderr are where a program reads its input, writes its normal output and writes its errors. In all 247 recorded runs the prompt went in on stdin and was in none of the argument lists, the flags and values after the command. Our view, not tested: a prompt on stdin needs no shell quoting.
- Save the stream. With
--output-format stream-jsonClaude Code prints its work as JSON lines, one JSON object per line. Each line is an event, and the lines together are the stream. The 247 recorded streams held 9 kinds of line, and without partial messages the median stream was 10 lines. Keep stdout, stderr and the exit code of every run. - Read the first line. The first line is
system/init: the model, the tools, the permission mode, where the credentials came from (apiKeySource,nonein all 247, the subscription login) and the Claude Code version. It was first in 247 of 247 streams. - Read the last line and the denial list, not the exit code alone. The last line is
result, withsubtype,is_error,terminal_reasonandpermission_denials. A permission prompt is Claude Code asking a person whether it may run something. With nobody there it denies the request and the run goes on, so a refused command can sit inside a run that exits 0. That happened in 39 of the 247 recorded runs. - Read the model back.
--modelis a request. A model read-back is reading the model's name from the stream instead of trusting the flag. Four places in the stream name the model, and in 247 of 247 recorded runs all four equalled the flag. - Check the work itself. One recorded bug-fix run exited 0, said success and changed nothing, and a script that reads only the exit code would have scored it as fine. After every run, check the real result. For a bug fix that means running the tests.
The result, on one screen
I'm Claude Sonnet 5.5, and I wrote this page from the saved files of 247 recorded claude -p runs. I did not make a new run. This is what the files show.
Measuredn = 247 recorded runs Counted from the 247 saved run files: one Windows PC, Claude Code 2.1.284, a subscription login and no API key, no new run. The 39 and the 1 come from the result lines and the scores of the 75 bug fixes.
Measured How the headline was measured
- Measured
- For each of the 247 saved runs: the process exit code, whether anything reached stderr, the
subtypeandis_errorof the result line, and whether a command was refused. For each of the 75 bug fixes: whether the repository passed the held-out tests (tests the run did not see, run afterwards) and whether any file changed. - Method
- A script counted each run's saved files. The runs were made earlier, one at a time, by a script on one Windows PC with Claude Code
2.1.284and a subscription login. No new run was made. - n
- 247 runs, 75 of them bug fixes. These are counts, so no interval is claimed.
- Source
- The saved run files, summarised in the evidence list this page was written from, read 2026-10-10.
- Excludes
- Any other computer, version or login. What the exit code means on a failing run: none occurred.
All 247 on one grid
Measuredn = 247 recorded runs The 38 and the 1 are the 39 runs with a refused command, split by whether the repository then passed the held-out tests, which the run did not see.
What the 247 runs were
Seven kinds. 4 were probes, one-line prompts such as "Reply with exactly: OK". 72 were a coding battery, a set of coding tasks. 75 were bug fixes: five repositories, five tries each, with each of three models. 31 were speed and effort tests, 29 were design tasks, 12 were chart-reading tasks and 24 asked for a summary of pasted content. Class 98: AI Bug Fix teaches the bug-fix loop; this class reads the same kind of run from a script's side.
How they were run
A script, which I call the harness, started each run as a fresh process, one at a time: none began before the one before it had ended. Every run reported Claude Code 2.1.284, and apiKeySource was none in 247 of 247, which means the subscription login and no API key. Every run used --output-format stream-json, and ten flags were in every run (see the command). 151 runs had no tools, 21 could only read files, and the 75 bug fixes had Read, Edit and Bash. All 39 runs with a refused command are among those 75.
What this does not show
- Your setup. One Windows PC, one Claude Code version and a subscription login. The vendor's pages describe versions up to
2.1.296, and the runs used2.1.284. - A failing exit. No run ended with a non-zero code, so this class has no recorded example of one. What the vendor says a non-zero code means is in Exit 0, labelled as documented.
- A rate. 247 runs say what happened in these runs, not how often it happens.
- A model comparison. The runs used three
--modelvalues, and this class does not compare them.
The command
Headless means Claude Code runs with no person at the keyboard and no terminal screen. You start it from a script, from a scheduler (a service such as cron that runs a script at set times) or from CI (a service that runs your scripts on every code change), and it has to finish without help. claude -p is the way in: the prompt goes in on stdin, the answer comes out on stdout, errors go to stderr, and the exit code comes last. The vendor's headless page says "Non-interactive mode reads stdin, so you can pipe data in and redirect the response out like any other command-line tool."
The recorded runs fed their prompts in that way: none of the 247 argument lists holds the prompt, and the harness script's own description says it came in on standard input. The flags are the rest of the command. The runs used 15 different flags, and 10 were in every run.
| What the flags do | Flags | Runs |
|---|---|---|
| Run headless, name the model, set the system prompt (the standing instruction a run starts with) | -p --model --system-prompt | 247 each |
| Choose what is printed | --output-format --verbose | 247 each |
| Choose the tools, the things the model can use | --tools | 247 |
Pre-approve actions with allow rules, patterns such as Bash(python -m pytest *) that approve matching commands; start in restricted mode | --allowedTools --restricted | 75 each |
| Decide what happens when a permission prompt would be needed | --permission-prompts | 247 |
| Keep the run clean: no customizations, no other MCP configuration, no saved session | --safe-mode --strict-mcp-config --no-session-persistence | 247 each |
| Optional: effort level, partial text as it is generated, a debug log | --effort --include-partial-messages --debug-file | 16, 15, 1 |
MeasuredReportedn = 247 recorded runs Counts are from the 247 saved argument lists. The first column paraphrases the vendor's saved CLI reference, read 2026-10-10. Documented, not tested on your install.
Two tools follow. The builder writes a command in PowerShell and bash, and marks each flag as recorded or only documented; it runs nothing. Under it, a drill takes apart the argument list of the walkthrough run: the one bug fix, of the 75, that left the repository unchanged. I come back to that run throughout the class.
Build the command
Choose options and the builder writes the claude -p command in PowerShell and bash, then lists what each part does and where that comes from. Recorded means the flag was in the recorded runs: 247 runs, one Windows PC, Claude Code 2.1.284, a subscription login, no new run. Documented means paraphrased from the vendor’s saved pages and not run here.
As in all 247 recorded runs, the prompt goes in on stdin, so it is not part of the command. Everything happens in your browser: nothing is sent anywhere, and this page does not run claude.
PowerShell
Quoted for Windows PowerShell 5.1, the one tested here.
bash
Quoted for bash, tested with the bash that comes with Git for Windows.
Prompt, sent on stdin
What each part does
Checks
MeasuredQuotedn = 247 recorded runs Real: the bug-fix preset is one recorded run’s argument list, and each Recorded badge counts the runs that passed that flag (one Windows PC, Claude Code 2.1.284, subscription login, no new run). Quoted: Documented rows paraphrase or quote the vendor’s saved pages and were not run here. Constructed by us: the other two presets and every check.
MeasuredQuoted How the builder was made
- Measured
- How many of the 247 recorded runs passed each Recorded flag, and what the vendor’s saved pages say each flag does.
- Method
- Counts come from each run’s argument list. Meanings are paraphrased from the saved vendor pages and the installed CLI’s help; quoted sentences are word for word. The commands are written in your browser and were tested here by running them through Windows PowerShell
5.1and bash against a stub program that prints the arguments it receives.claudewas not run. - n
- n = 247 recorded runs, one Windows PC, Claude Code
2.1.284, a subscription login, no new run. A Documented row has no run behind it. - Source
- The saved argument lists of the 247 runs; four saved vendor pages and the installed CLI’s help, read 2026-10-10.
- Excludes
- That a documented flag works as described on your setup: the docs describe versions up to
2.1.296, the runs used2.1.284. How your shell quotes: tested on one Windows PC with Windows PowerShell5.1and bash only. The checks are our reading of the docs, not CLI output.
Take the recorded command apart
The argument list of the walkthrough run, one of the 247 recorded runs, written as one command and cut into regions. The list is recorded; quote marks are added where a shell needs them. Select a region to see what it does and how many of the 247 runs used it. Nothing is scored. The notes paraphrase the vendor's pages (saved 2026-10-10) and are ours; the counts are measured.
The walkthrough run's argument list, in order:Not in the list: the prompt. It went in on standard input in all 247 runs (recorded). This run's:The test in tests/test_visible.py fails. Find and fix the bug. Do not edit the tests.Used by other recorded runs, not in this line:
-p (long form --print) runs Claude Code without the interactive screen: it takes the prompt, does the work, prints the result and exits. All 247 runs were started this way, each a fresh process.--model takes an alias such as sonnet or a full name. All 247 runs passed a full name, 3 different values in all. The stream says which model answered, and later on this page you read it back.stream-json prints newline-delimited JSON as the run goes, so each step is one line, an event. --verbose is the logging flag the vendor's streaming examples pair with it. Both were in all 247 runs: 247 streams, 13033 lines between them.--safe-mode starts with customizations off (CLAUDE.md, skills, plugins, hooks and MCP servers, which are add-on tool servers, do not load); login, model choice, built-in tools and permissions work normally. --strict-mcp-config limits MCP servers to those named by --mcp-config, which no run passed. --no-session-persistence keeps the session from being saved, so it cannot be resumed. Our reading: the first two keep this machine's own setup out.--tools names them (this line names Bash), keeps the file tools inside the working folders and loads only managed settings and --settings. In 75 of the 247 runs.2.1.259 or later; the runs used 2.1.284. In all 247 runs.Read and Edit allow those tools. Bash(python -m pytest) allows that one exact command. Bash(python -m pytest *) allows it followed by anything: a space and a star at the end match any text after it. Each of the 75 bug-fix runs passed these four rules; no other run passed any. Read-only commands needed no rule (98 ran with none). A request that needs approval and no rule covers is denied, because nobody can answer.--system-prompt replaces the whole default system prompt, the instructions Claude Code starts with, with your text. In all 247 runs; the text shown is the walkthrough run's.stream-json (the CLI reference). Passed in 15 runs, whose streams hold 10112 partial-message lines between them; the other streams hold none.Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.
QuotedMeasuredn = 247 recorded runs Argument list recorded (the walkthrough run); counts measured from all 247 run records: 247 recorded claude -p runs, one at a time on one Windows PC, Claude Code 2.1.284, subscription login (no API key); no new run was made. The harness script's own description says the prompt goes in on stdin, and none of the 247 argument lists holds it. 10 of the 15 flags were in every run; the other five are --restricted, --allowedTools, --effort, --include-partial-messages and --debug-file. All 15 are in claude --help of that version. The notes paraphrase the vendor's saved pages and are ours. Documented, not tested on your install.
Choose the output format
Every recorded run used stream-json. The other two formats are documented, not run here.
| Format | The vendor's words | Recorded runs |
|---|---|---|
text | "plain text output", the default | 0 of 247 |
json | "structured JSON with result, session ID, and metadata" | 0 of 247 |
stream-json | "newline-delimited JSON for real-time streaming" | 247 of 247 |
QuotedMeasuredn = 247 recorded runs Words from the vendor's saved headless page, read 2026-10-10. Run counts are measured. Documented, not run here: text and json.
Which output format fits the job?
Nine jobs, each with the output format someone chose. Read PASS (the button says ship) as: the format fits the job. Read FAIL (send back) as: it does not. Every job is constructed for this drill; the answers come from the vendor's headless page and CLI reference.
Job: put one answer into a shell variable. Chosen: --output-format json, then read the result field with a JSON tool.
claude -p "Summarize this project" --output-format json | jq -r '.result'
json returns structured JSON with the result, the session ID and metadata, and the text sits in the result field. Documented, not run here.Job: show the answer to a person as it is being written, word by word. Chosen: --output-format json.
claude -p "Explain recursion" --output-format stream-json --verbose --include-partial-messages
json is described as one structured result. The page names stream-json for real-time streaming, and it adds --include-partial-messages to receive tokens as they are generated. Documented, not run here.Job: show the text as it is generated. Chosen: --output-format stream-json --verbose --include-partial-messages.
Job: keep a log of every tool call for later. Chosen: --output-format stream-json.
Job: keep a log of every tool call for later. Chosen: --output-format text.
text is described only as plain text output, and the saved pages describe no per-event records for it. The per-event stream, with the tool calls in it, is what the page documents for stream-json.Job: print the answer for a person at a terminal. Chosen: --output-format stream-json.
stream-json prints one JSON object per line, which suits a program that reads events. For a person who wants the answer, text is the default and is plain text output. That stream-json is a poor fit for a person is our judgement, not the vendor's statement.Job: a script must find out whether any command was refused. Chosen: --output-format stream-json, then read permission_denials in the last line.
Job: a script must find out whether any command was refused. Chosen: --output-format text.
Job: output that must match a schema. Chosen: --output-format json with --json-schema, then read structured_output.
structured_output field. An invalid schema makes the command exit with an error. Documented, not run here.Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.
QuotedMeasured Where each answer comes from
- Measured
- Nine constructed jobs. Constructed for this drill. Not a recorded run. Counts in the explanations are recorded.
- Method
- PASS: the format fits as the vendor's pages describe it. Documented, not run here.
- n
- 9 jobs. Counts from 247 recorded
claude -pruns, one at a time on one Windows PC, Claude Code2.1.284, subscription login (no API key); no new run was made. No interval is claimed. - Source
- Run Claude Code programmatically and CLI reference (saved 2026-10-10); the run records; the walkthrough stream.
- Excludes
- Other versions. The text format: no recorded run used it.
Documented, not run here
No recorded run used --bare, --output-format json, --json-schema, --max-turns, --max-budget-usd, --resume, --continue or --input-format. What this class says about them is paraphrased or quoted from the saved vendor pages. Those pages describe versions up to 2.1.296, and the runs used 2.1.284. 15 of the 74 flags in the CLI reference's table are not listed by the installed CLI's --help. --max-turns is one of them: the saved help outputs do not list it, though the reference documents it. --bare is covered in Startup.
The stream: what a run prints
The stream is the instrument panel. The 247 recorded streams held 9 kinds of line. system/init was the first line and result the last in 247 of 247, and every stream held a rate-limit line. A script can find those lines by their type.
Measuredn = 247 recorded streams The 9 kinds are the distinct type and subtype pairs found in 13,033 lines.
| Line | What it is for |
|---|---|
system/init | The session starts. A session is the conversation inside one run. The line names the model, tools, permission mode, credentials and Claude Code version. |
assistant | The model speaks: text, a thinking block or a tool call. A tool call is one thing the model asks Claude Code to do, such as read a file, edit a file or run a shell command. A turn is one round of the model answering, possibly with tool calls. 950 lines in all. |
user | A tool result goes back to the model, a refusal included. 420 lines, in 96 streams. |
system/permission_denied | Claude Code refused a tool call. The line a script should watch. 41 lines, in 39 streams. |
rate_limit_event | The usage meter for a rate-limit window, a period (five hours, seven days) in which your plan allows a share of usage. It is about the plan, not the task. 250 lines, 1 to 3 in every stream. |
system/thinking_tokens | A running estimate of the tokens the model spends thinking (tokens are the small pieces of text a model reads and writes). 751 lines, in 141 streams. |
system/status | In 15 streams. This class does not use it. |
stream_event | A piece of the response as it is generated, which the vendor ties to --include-partial-messages. 10,112 lines, in 15 streams; 15 runs passed that flag. |
result | The last line: how the run ended, with the cost estimate, the model usage, the refusals and the final text. |
MeasuredReportedn = 247 recorded streams Counts are measured. The right-hand column is our reading of the recorded lines and the vendor's saved headless page.
In system/init, read model, tools, permissionMode (default in 247 of 247), apiKeySource (none in 247 of 247) and claude_code_version (2.1.284 in all). The vendor says it "is the first event in the stream unless startup events precede it", such as hook messages. That did not happen here, and all 247 runs used --safe-mode, under which the vendor says hooks do not load (hooks set by a managed policy still apply).
In result, read subtype (success in 247), is_error (false in 247), terminal_reason (completed in 247), permission_denials, total_cost_usd, modelUsage and result, the final text. All 247 results also had stop_reason end_turn and api_error_status null. The rate-limit lines said allowed in 249 of 250 cases. One said allowed_warning for the seven_day window, and Usage covers it.
Streams cost space. Without partial messages the median stream was 10 lines. The 15 runs with --include-partial-messages added 10,112 stream_event lines between them. Next, read one whole stream, the walkthrough run's, and then find the lines a script needs.
Read the stream
Below is the whole stream of one recorded claude -p run: 16 lines, one JSON object each. The agent was asked to fix a failing test in a small Python repository. It is the one run among the 75 recorded bug-fix runs that left the repository unchanged. Six times I ask you to guess before I show the line.
One recorded run. Paths, ids and the usage figures of the account that made it are removed. Nothing was run for this page. It is one of the 247 recorded runs (Claude Code 2.1.284, one Windows PC, a subscription login). One run is one example: it does not show how often runs go this way.
Dark boxes with a sage edge are the saved record; Our note boxes are ours. Plain explains a line, Raw shows its JSON with the keys to read marked.
Scripts are off, so you are reading the stream as a transcript: each line with the key fields of its JSON. The full JSON of each line is in the Raw view, which needs script.
Jump to a line you have reached
Before line 1 The command and the request
This is the argument list the harness passed to Claude Code in this run, one flag to a row. Quotes are added around items that contain spaces.
claude -p
--model claude-sonnet-5-5
--output-format stream-json
--verbose
--tools Read,Edit,Bash
--safe-mode
--strict-mcp-config
--no-session-persistence
--restricted
--permission-prompts none
--allowedTools Read Edit "Bash(python -m pytest)" "Bash(python -m pytest *)"
--system-prompt "You are a helpful assistant. Answer the user's request directly."
Our note Two items matter for what follows. --permission-prompts none is the setting for runs where nobody can answer a permission prompt. The saved headless page says: “In a -p run with no host, these requests are denied either way, and the flag also tells Claude not to retry them.” All 247 recorded runs passed the flag, so these runs cannot show what it changes. --allowedTools lists four allow rules (patterns that pre-approve matching commands): Read, Edit, Bash(python -m pytest) and Bash(python -m pytest *). Other flags are explained in the command. The prompt is not in the argument list: in all 247 recorded runs it went in on standard input.
-
Line
1The session startssystem/initThe first line of every stream. It names the model that will answer, the tools the run was given, the permission mode, and where the credentials came from.
model: claude-sonnet-5-5tools: ["Bash","Edit","Read"]permissionMode: defaultapiKeySource: noneOur note Read four fields.
modelisclaude-sonnet-5-5, the model that will answer: read it back, do not assume it (see Which model).toolslists three tools.permissionModeisdefault.apiKeySourceisnone: the subscription login was used, no API key. All 247 recorded streams began with this line.The key named
...is ours: it marks fields left out of this copy. -
Line
2The agent asks to run a commandassistantThe model's first move: a tool call. It lists the files, prints the failing test, and runs the tests in one shell command.
command: git ls-files && cat tests/test_visible.py && python -m pytest -q 2>&1 | tail -20Our note Several parts joined by
&&and a pipe, and it ran. The docs listcat,tailand read-only forms ofgitamong commands that run without a permission prompt (saved permissions page), and the test run fits the allow ruleBash(python -m pytest *). -
Line
3The usage meter reports inrate_limit_eventA status line about your plan limits, not about the task. It arrives between the model's steps and carries the window it is measuring and whether extra usage is in play.
status: allowedrateLimitType: five_hourisUsingOverage: falseOur note Three fields tell you what this line is:
statusisallowed,rateLimitTypeisfive_hour(the window it measures: a period in which a plan allows a share of use) andisUsingOverageisfalse. The twoutilizationvalues are a placeholder: they were the account owner's own usage, which this page does not publish. All 247 recorded streams had at least one, and a usage guard reads them (see Usage). -
Line
4The command's output comes backuserThe tool result, sent back to the model as a user message. The test fails: the code returned one cent too few.
Our note A
userline is not a person typing: here it is the command's output on its way back to the model. The failure to read isAssertionError: 1998 != 1999. The marker...(the test file is printed here)...replaces part of the output and is ours. -
Line
5A second readassistantThe agent searches for the function that failed.
command: grep -rn "def to_cents" -A6 --include=*.py . -
Line
6The search resultuserOne function, one line that converts dollars to cents by truncating.
-
Line
7The agent says what it foundassistantA text message in the middle of the run. The model has found the cause and says so before acting.
text: `int()` truncates (19.99*100 = 1998.99...). Use round, which also handles negatives. -
Line
8The agent asks to edit the fileassistantThe fix, as one shell command: an in-place edit chained with a test run by the shell operator that joins two commands.
command: sed -i 's/return int(amount \* 100)/return int(round(amount * 100))/' pricecents/money.py && python -m pytest -q 2>&1 | tail -2 -
Line
9The CLI refusessystem/permission_deniedNobody is there to approve, so the request is denied on the spot. This is the line a script should watch for.
tool_name: Bashdecision_reason_type: asyncAgentdecision_reason: no approval surface in this session; permission request denied automaticallyOur note Read
decision_reason:no approval surface in this session; permission request denied automatically. All 41 denial lines in the 247 recorded streams give this reason (39 runs, every one aBashcall). Every run passed--permission-prompts none, so a denial does not show what that flag does. Themessagegoes back to the model on the next line. It saysThe action was NOT performed; do not claim it succeededanddo not retry it; both are in all 41 denial messages.The allow rules this run was given, from its argument list:
ReadEditBash(python -m pytest)Bash(python -m pytest *)
No rule covers
sed; theEdittool is allowed, and the agent did not use it. The docs say “A rule must match each subcommand independently.” (quoted from the saved permissions page; documented, not tested here). The command on line 8 has an edit part and a test part. The edit part has no rule, so the whole command was refused, test run included. The message names the part that needed approval; it starts atsed -i. -
Line
10The refusal goes back to the modeluserThe same text is returned to the model as an error result, so it can tell the user what happened.
is_error: trueOur note The refusal returns to the model as an error result:
is_erroristrueon this tool call. The run's ownis_erroris a separate field on the last line. -
Line
11A thinking-token estimatesystem/thinking_tokensA running estimate of the tokens the model is spending on thinking. It is an estimate, not the final count.
-
Line
12Another estimatesystem/thinking_tokensThe estimate after the model has thought for longer.
-
Line
13A thinking blockassistantThe thinking block is in the stream, but its text is empty here; only a signature travels with it.
-
Line
14The agent reportsassistantThe final message. It matches the record: the edit was refused and nothing changed. A person reading the log sees this. A script that checks only the exit code does not.
Our note Check three sentences against what you saw.
I couldn't apply the fix because the edit was denied.matches line 9.Nothing in the repo has changed.matches what was found afterwards: 0 files changed.I haven't run the tests with this change.matches the stream: no test ran after the refusal. The message also offers a one-line fix,return int(round(amount * 100)), for a person to apply. -
Line
15The meter reports againrate_limit_eventA second meter line, just before the end.
-
Line
16The resultresult/successThe last line. Its status says success, the process will exit 0, and the cost is an estimate. The one entry in permission_denials is the whole story of this run.
subtype: successis_error: falseterminal_reason: completedOur note Fields to read, in order:
subtypeissuccess,is_errorisfalse,terminal_reasoniscompleted;permission_denialsholds one entry, the whole command from line 8.total_cost_usdis the CLI's own estimate at list price (nominal), not spend.These fields say how the run ended, not that the work was done. Checked afterwards, outside the stream: 0 files changed, and the failing test still failed.
After line 16 The process ends: what a script sees
- Exit code
0- Standard error
0bytes- The record says
- the process exited with code 0
The process handed back an exit code of 0 with nothing on standard error. A script that checks only the exit code reads this run as fine. It would read all 247 recorded runs the same way: in every one the exit code was 0 and standard error was empty. That includes the 39 runs in which a command was refused, and this run, where nothing in the repository changed. Of those 39, 38 still passed the held-out test suite; this is the other one.
What to read instead, in this order:
- The
resultline:subtype,is_errorandterminal_reason. Here they weresuccess,falseandcompleted, and the work was still not done. permission_denialson the same line. The docs say the final result message lists the denials there (saved headless page). Here it holds one entry.- The final message. Here it said the edit did not happen. A script cannot judge prose, so keep it for the person who reads the log.
- The repository itself: run the tests. Of the four, only this one looks at the work and not at a report about it.
Our note Documented, not seen in these runs: “Claude Code exits with code 0 on success and a non-zero code when the run fails, so your scripts can branch on the exit status.” (quoted from the saved headless page). None of the 247 recorded runs exited with a non-zero code, so this class has no recorded example of a failing exit.
Go on to Exit 0 is not done and Permissions with nobody to ask.
MeasuredQuotedn = 1 run The 16 lines, the exit code and the size of standard error are the saved record of one run, with paths, ids and usage figures replaced by placeholders. Sentences from the vendor's saved pages are quoted and say so. Our note marks what we wrote.
MeasuredQuoted How the stream reader was made
- Measured
- What one run printed: its 16 lines in order, the exit code of the process and the size of its standard error.
- Method
- Each line was copied from the saved stream. Where a field held a path, an id, the account's usage or a long list, the copy shows a placeholder in angle brackets or a marker with three dots. The title and plain sentence under each line, the questions and the boxes marked Our note are our teaching text. The raw view is the saved JSON of each line, laid out with indentation.
- n
- n = 1 run. One run is one example and claims no confidence interval. It is not a typical run: it is the one bug-fix run among the 75 that left the repository unchanged.
- Source
- The saved stream of one of the 247 recorded runs (Claude Code 2.1.284, one Windows PC, a subscription login), recorded in October 2026. It is the one bug-fix run whose score file says it did not pass: its argument list, event count, exit code and stderr size match that run's record. No new run was made for this page. That the prompt went in on standard input is the harness script's own description; that it was in no argument list comes from comparing all 247 with the prompt files. Vendor sentences are quoted from the saved headless guide and permissions guide.
- Excludes
- The removed paths, ids and usage figures; the fields marked as left out on line 1; the model's thinking text, which is empty in the stream; the part of one tool output that printed a test file. It does not show how often runs go this way, and it says nothing about your own setup.
Find the line that tells you
Three rounds over lines of one recorded stream, the walkthrough run (16 lines). Each round prints a few lines, shortened (cut values and fields are marked with three dots), and asks which one holds what a script needs. The lines are recorded; the questions are constructed.
Which line tells a script the model, the tools and where the credentials came from?
1 system/init "model": "claude-sonnet-5-5", "tools": ["Bash", "Edit", "Read"], "apiKeySource": "none", ...
2 assistant "message": {"model": "claude-sonnet-5-5", "content": [{"type": "tool_use", "name": "Bash", "input": {"command": "git ls-files && cat tests/test_visible.py && python -m pytest -q 2>&1 | tail -20"}}], ...}
16 result/success "subtype": "success", "is_error": false, "terminal_reason": "completed", "total_cost_usd": 0.020248600000000002, ...model names the model, tools lists the tools the run was given and apiKeySource says where the credentials came from; none means, in these runs, the subscription login. Recorded: system/init was the first line in 247 of 247 streams. Line 2 names the model too, but it carries a tool call, not the session's setup.Think of the session's setup, before any work.
Which line holds the run's cost estimate?
11 system/thinking_tokens "subtype": "thinking_tokens", "estimated_tokens": 50, "estimated_tokens_delta": 50, ...
15 rate_limit_event "rate_limit_info": {"status": "allowed", "rateLimitType": "five_hour", "isUsingOverage": false, ...}
16 result/success "total_cost_usd": 0.020248600000000002, "modelUsage": {"claude-sonnet-5-5": {..., "costUSD": 0.020248600000000002, ..., "costBasis": "list"}}, ...total_cost_usd is the run's cost estimate, and costUSD inside modelUsage is the same figure for the one model; costBasis says list, so the tokens were priced at list price: an estimate, not spend. Line 11 is an estimate too, but of thinking tokens. The vendor says these figures can differ from your actual bill.One line is about money; another estimates something else.
Which line is the CLI's own notice, as it happened, that a command was refused?
8 assistant "message": {"model": "claude-sonnet-5-5", "content": [{"type": "tool_use", "name": "Bash", "input": {"command": "sed -i 's/return int(amount \\* 100)/return int(round(amount * 100))/' pricecents/money.py && python -m pytest -q 2>&1 | tail -2"}}], ...}
9 system/permission_denied "tool_name": "Bash", "decision_reason_type": "asyncAgent", "decision_reason": "no approval surface in this session; permission request denied automatically", ...
10 user "message": {"content": [{"type": "tool_result", "is_error": true, "content": "Permission for this tool use was denied. ...", ...}]}system line with the subtype permission_denied, written by Claude Code itself when the refusal happened; it names the tool and the reason. Line 10 carries the same text to the model, as a tool result, and line 8 is the request that was refused. Recorded: 41 such lines in 39 of the 247 streams, all for Bash, all with this reason.The CLI writes its own record, with a subtype.
Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.
MeasuredQuoted What is recorded and what is constructed
- Measured
- Recorded: 3 rounds over one stream of 16 lines, shortened. The questions are constructed. Constructed for this drill. Not a recorded run.
- Method
- The flagged line is the one the saved pages name for that job.
- n
- 3 rounds, 1 stream. 247 recorded
claude -pruns, one at a time on one Windows PC, Claude Code2.1.284, subscription login (no API key); no new run was made. No interval is claimed. - Source
- The walkthrough stream; the run records; Run Claude Code programmatically (saved 2026-10-10).
- Excludes
- Other streams. Usage values: the rate-limit lines are shown without them on purpose.
Which model answered
--model is a request, not a receipt. Four places in the stream name the model that answered, and in the 247 recorded runs all four equalled the run's own flag.
| Place in the stream | What it holds | Equal to the flag |
|---|---|---|
model in the system/init line | the model the session starts with | 247 of 247 runs |
message.model in every assistant line | the model that wrote that message | 247 of 247 runs |
the key in modelUsage in the result line | the model whose usage was counted; exactly one key | 247 of 247 runs |
canonicalModel beside it | a second name field in the same entry | 247 of 247 runs |
Measuredn = 247 recorded runs, 950 assistant lines Each count is the runs in which that place named the same model as that run's own --model value. Every run passed a full model name, and no run passed an alias such as sonnet.
A match in all 247 does not mean your runs will match. The vendor's model page describes a fallback model that Claude Code can switch to when the primary one is overloaded or unavailable. It also says that when the requested model is scheduled for retirement or is remapped to a newer version, the warning on stderr is suppressed for json and stream-json output, and it tells you to read the actual model from the modelUsage field of the result line. That is documented, not run here: no run in this set differed from its flag.
So read the name back and log it next to the run's other results. The check is a few lines of script: compare each place with the flag you passed, and treat a difference as a finding, not a detail.
Does the read-back pass?
The flag is a request. Four places in the stream state which model answered: the system/init line, every assistant line, the one modelUsage key and its canonicalModel. Read PASS (the button says ship) as: every place names what the flag asked for. Read FAIL (send back) as: one does not, or the check cannot be run as written.
Recorded: the walkthrough run. The flag and the four places in its stream.
flag claude-sonnet-5-5
init claude-sonnet-5-5
assistant claude-sonnet-5-5
usage key claude-sonnet-5-5
canonical claude-sonnet-5-5
Recorded: line 13 of the walkthrough stream is an assistant line whose thinking text is empty. Does it carry the model name?
message.model, even this one, so a check that skips lines with no text would miss it. All 950 assistant lines matched the flag.Constructed: the flag asked for claude-sonnet-5-5 and the init line names model-x.
flag claude-sonnet-5-5
init model-x
assistant claude-sonnet-5-5
usage key claude-sonnet-5-5
canonical claude-sonnet-5-5
Constructed: the init line and the result line match the flag, but one assistant line in the middle names model-x.
flag claude-sonnet-5-5
init claude-sonnet-5-5
assistant model-x (one line)
usage key claude-sonnet-5-5
canonical claude-sonnet-5-5
Constructed: the result line's modelUsage holds two keys.
flag claude-sonnet-5-5
init claude-sonnet-5-5
assistant claude-sonnet-5-5
usage key claude-sonnet-5-5 and model-x
canonical claude-sonnet-5-5
Constructed: the flag is the alias sonnet, and the init line names a full model name.
flag sonnet
init claude-sonnet-5-5
assistant claude-sonnet-5-5
usage key claude-sonnet-5-5
canonical claude-sonnet-5-5
--model takes either. Every recorded run passed a full name, so these runs needed no alias table. Pass a full name to compare like with like.Constructed: the first line of the stream is not system/init.
Constructed: your script writes all five names to the log next to the run's other results, and they match.
Constructed: your script skips the read-back, because the flag already says which model to use.
Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.
MeasuredQuoted What is recorded and what is constructed
- Measured
- Nine rows: 2 recorded from the walkthrough stream, 7 constructed. Constructed for this drill. Not a recorded run. Across all 247 runs the init model, all 950 assistant lines, the one modelUsage key and the canonicalModel each equalled the flag (247 of 247 runs each).
- Method
- FAIL: a place differs, or the comparison cannot be made as written. Mismatches use the made-up name
model-x, so they say nothing about any real model. - n
- 9 rows. 247 recorded
claude -pruns, one at a time on one Windows PC, Claude Code2.1.284, subscription login (no API key); no new run was made. No interval is claimed. - Source
- The walkthrough stream; the run records; Model configuration and CLI reference (saved 2026-10-10).
- Excludes
- Your install: the read-back finds out. Any mismatch in a recorded run: there was none. Any model comparison: none made.
Permissions with nobody to ask
In a terminal, Claude Code asks before it runs a command. A headless run has nobody to answer. The vendor's flag for that case is --permission-prompts none. The vendor's CLI reference says "Pass none when nobody can answer, and Claude Code denies them instead." The headless page adds: "Anything that would prompt is denied unless a PermissionRequest hook allows it, Claude is told that nobody can approve the request and not to retry it, and the run continues." The flag needs Claude Code 2.1.259 or later. It was in all 247 recorded runs, which used 2.1.284, and the walkthrough run passed none. The same page adds: "In a -p run with no host, these requests are denied either way, and the flag also tells Claude not to retry them." All 247 runs passed the flag, so they cannot show what it changes.
The bug-fix runs had four allow rules: Read, Edit, Bash(python -m pytest) and Bash(python -m pytest *). The last one, with its trailing space and *, matches any command that starts with python -m pytest. Anything else that needed approval was refused at once. Plain read-only shell commands ran without a rule, because Claude Code has a built-in read-only set.
What happened in the 75 bug-fix runs
| Outcome | Calls | What the call was |
|---|---|---|
| Ran | 108 | the allowed test command, python -m pytest |
| Ran | 98 | plain read-only shell commands (cat, ls, find, grep and the like), with no rule for them |
| Ran | 74 | the Edit tool |
| Ran | 68 | the Read tool |
| Refused | 18 | a shell command that edits a file: sed -i, or a Python script that rewrites it |
| Refused | 10 | a file list piped through xargs |
| Refused | 9 | python -c |
| Refused | 3 | python -m unittest, another way to run the same tests |
| Refused | 1 | find -exec |
| All | 389 | 348 ran and 41 were refused, every refusal a Bash call |
Measuredn = 75 bug-fix runs, 389 tool calls Counted from the saved runs. 39 of the 75 runs had at least one refusal, and every refusal carried the same reason, "no approval surface in this session; permission request denied automatically".
The 18 refused shell edits came from runs that also had the Edit tool allowed. 17 of those 18 runs then used the Edit tool and still fixed the bug. The 18th is the walkthrough run: all 3 of its tool calls were Bash, and it ended with the repository unchanged. In all, 38 of the 39 runs with a refusal still fixed the bug.
A rule is checked part by part
A compound command joins several commands with &&, ||, ;, |, |&, & or a new line. The vendor says "A rule must match each subcommand independently." Two commands from the walkthrough run show it:
ran git ls-files && cat tests/test_visible.py && python -m pytest -q 2>&1 | tail -20 refused sed -i 's/return int(amount \* 100)/return int(round(amount * 100))/' pricecents/money.py && python -m pytest -q 2>&1 | tail -2
Every part of the first line was a read-only command or matched the test rule, so it ran. The second ended with the same test command, but the sed -i edit before it matched no rule, and the whole line was refused. The message said the command "contains multiple operations". This is our reading of the permissions page; the stream shows what happened, not why. A rule matches the text of a command, not what the program does: the vendor warns that "Bash permission patterns that try to constrain command arguments are fragile", and for limits that do not depend on command text it points to sandboxing.
What the refusal told the model
A refusal is recorded in three places. A permission_denied line says Claude Code refused, and the result line lists it in permission_denials: 41 lines and 41 entries across the 247 runs. The same message goes back to the model as a tool result, and in all 41 denial messages it held "The action was NOT performed; do not claim it succeeded" and "do not retry it". In the walkthrough run the final message said the edit was denied and nothing in the repo had changed, and the process still exited 0. That message was prompted by the refusal text, so it is not evidence that models report honestly in general.
Do not fix a denial by switching checks off
A denial means a rule is missing: read it, then decide. --dangerously-skip-permissions skips permission prompts, and the vendor documents it, with bypassPermissions, for isolated containers and VMs only. No recorded run used either, and this class does not recommend them. The vendor's dontAsk mode denies every call that would otherwise prompt. It is documented, not run here.
Try the four rules on nine commands. Some are recorded, and the drill says which are constructed.
Would the allow list run this command?
The recorded bug-fix runs allowed four rules: Read, Edit, Bash(python -m pytest) and Bash(python -m pytest *). Nobody could answer a prompt, so a command that needs approval and no rule covers is denied. Read PASS (the button says ship) as: it runs without asking. Read FAIL (send back) as: it is refused. Recorded rows come from the walkthrough stream or the 75 bug-fix runs. Constructed rows are decided from the vendor's permissions page and were not run.
Recorded: the walkthrough run's first command (line 2 of its stream). Did it run?
git ls-files && cat tests/test_visible.py && python -m pytest -q 2>&1 | tail -20
git ls-files, cat and tail are in the read-only set, and python -m pytest -q 2>&1 matches the starred pytest rule (2>&1 points at no file).Recorded: the walkthrough run's fix (line 8 of its stream). Did it run?
sed -i 's/return int(amount \* 100)/return int(round(amount * 100))/' pricecents/money.py && python -m pytest -q 2>&1 | tail -2
sed -i. That part matches no rule, and one part with no rule is enough to refuse the whole line. Of the 18 bug-fix runs with a denied shell edit, 17 switched to the Edit tool and passed the held-out suite; this run, the one that fixed nothing, is the other.Recorded kind: 3 denied commands in the 75 bug-fix runs ran python -m unittest, another way to run the same tests.
python -m unittest ...
Bash(python -m pytest) matches one exact command, and Bash(python -m pytest *) matches commands that start with python -m pytest, so python -m unittest matches neither: a rule matches the command as written, not what it means. Their text is not shown here.Recorded kind: 10 denied commands in the 75 bug-fix runs piped a file list through xargs.
<a file list> | xargs <a command>
xargs. The vendor's page says a bare xargs is stripped before the rules are matched, but one with flags is matched as an xargs command. We do not know which form each took, so we do not say why. Their text is not shown here.Constructed: the agent asks to run the tests on one file.
python -m pytest tests/test_visible.py
Bash(python -m pytest *) matches any command that starts with python -m pytest and a space, so it matches. The vendor says a star at the end enables prefix matching. Constructed; decided by the rule, not run.Constructed: the agent asks to run the tests with no options.
python -m pytest
Bash(python -m pytest) matches this exact command. The vendor also says a star at the end, with a space before it, matches the bare command, so Bash(python -m pytest *) covers it too. Constructed; decided by the rules, not run.Constructed: the agent chains the tests to a second command.
python -m pytest && python build.py
python build.py, matches no rule, and a rule must match each part on its own, so the whole line is refused. Constructed; decided by the rules, not run.Constructed: the agent asks to run the tests without the python prefix.
pytest -q
python -m pytest. This is the same tool started another way, and a rule matches the command text as written, so none matches. Ask for the form your rule names. Constructed; decided by the rules as written, not run here.Constructed: the agent asks to list a folder.
ls tests
ls is in the read-only set the vendor lists, which runs without a prompt in every mode, so no rule is needed. Recorded: 98 plain read-only shell commands ran in the 75 bug-fix runs with no allow rule for them. This row is constructed, not run.Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.
MeasuredQuoted What is recorded and what is constructed
- Measured
- Nine commands: 2 recorded from the walkthrough stream, 2 recorded kinds with counts, 5 constructed. Constructed for this drill. Not a recorded run. Across the 75 bug-fix runs: 389 tool calls, 348 ran, 41 were refused, all Bash.
- Method
- A command ran if every part matched an allow rule or the read-only set. The reasons are our reading of the saved permissions page: the stream shows what happened, not why.
- n
- 9 rows; 389 tool calls in 75 bug-fix runs. 247 recorded
claude -pruns, one at a time on one Windows PC, Claude Code2.1.284, subscription login (no API key); no new run was made. No interval is claimed. - Source
- The walkthrough stream; the run records; Configure permissions (saved 2026-10-10).
- Excludes
- Other versions. The text of the commands behind the counts (not published). Constructed rows: decided from the page, not run.
Nobody can answer once an unattended job has started, so decide first what it may touch and what counts as done.
Write the spec for an unattended job
Eight fields. The spec updates as you type; copy it or download it as a text file. Write it before the job runs, because nobody will be there to answer once it has started. The defaults are a constructed example.
Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.
QuotedReportedn = 1 template The layout is our own suggestion; no tool reads it. The rules restate the vendor's saved headless page: documented, not run here. Nothing is sent anywhere; the file is built in your browser.
Exit 0 is not done
One of the 75 bug-fix runs did not pass the held-out tests, the ones the agent had not seen, and it is the run you can read line by line in the stream. It left its repository unchanged. I call it the run that fixed nothing. It shows what an exit code tells you and what it does not.
What the run that fixed nothing did
The prompt was one line: The test in tests/test_visible.py fails. Find and fix the bug. Do not edit the tests. The agent made three tool calls, all shell commands. The first listed the files, printed the failing test and ran the tests. The second searched for the function. Both ran. The agent then said what it had found: int() cuts decimals off where it should round.
The third command was the fix: an in-place edit with sed -i, joined by && to a test run. Claude Code refused it on the spot. Nobody was there to approve it, and no allow rule covered the sed -i part. The refusal said "The action was NOT performed; do not claim it succeeded". The final message did as it was told. It said the edit was denied, that "Nothing in the repo has changed.", gave the one-line fix and said the tests had not been run with the change.
Then the process ended with exit code 0 and an empty stderr. The result line said subtype success, is_error false and terminal_reason completed. The repository was unchanged and the visible test still failed.
What a script saw
| What a script can read | All 247 recorded runs | The run that fixed nothing |
|---|---|---|
| Process exit code | 0 in 247 | 0 |
| stderr | empty in 247 | empty |
Result subtype | success in 247 | success |
Result is_error | false in 247 | false |
Result terminal_reason | completed in 247 | completed |
permission_denials | 41 entries, in 39 runs | 1 entry |
| Final message | text for a reader, not a field to test | "Nothing in the repo has changed." |
Measuredn = 247 runs Read from each run's saved stream, exit code and stderr. The right column is the walkthrough run. It matches the one bug-fix run scored unsolved on argument list, event count, exit code and stderr size. Its final message is line 14 of the stream. One Windows PC, Claude Code 2.1.284, subscription login; no new run. Your runs may differ.
In the 39 runs where Claude Code refused a command, the exit code was 0 every time and is_error was true in none. Of those 39, 38 still passed the held-out tests. This was the one that did not.
A script that checked only the exit code would have scored all 247 runs as fine, the 39 and the run that fixed nothing included. That is not a fault of the exit code. It says the process finished, and the process did finish. Whether the work got done is a different question.
What to check, in this order
- The exit code. It tells you the process ended.
- The last line of the stream. In the
resultline,subtypeshould besuccess,is_errorfalseandterminal_reasoncompleted. - The denials. Any
permission_deniedline, and any entry inpermission_denials. A denied action was not performed, so some of the work may be undone. - The final message. It is the agent's own account. Read it as a report, not as proof. In the run above it said exactly what happened, and the bug was still there.
- The work itself. Check the work itself: run the tests, or whatever check this job really needs. Nothing above replaces this step.
What the docs say a non-zero exit means
The saved headless page describes failing runs in these sentences. They are documented, not seen in these runs: no recorded run exited with a non-zero code.
- "Claude Code exits with code 0 on success and a non-zero code when the run fails, so your scripts can branch on the exit status."
- "When a failure happens inside the run, such as missing authentication, Claude Code prints the failure as the result on stdout."
- "If you pass an invalid flag, Claude Code reports the error to stderr before the run starts."
Quotedn = 0 recorded runs with a non-zero exit From Run Claude Code programmatically, saved 2026-10-10. The page also describes two more cases, documented and not run here: piped input over 10MB exits with a non-zero status, and a run stopped with SIGTERM exits with code 143.
The second sentence matters to a script. When a run fails inside, the reason comes out on stdout as the result, so read that when the code is not 0, and do not rely on the code alone. The drill below runs six finished runs through six checks, in order. The first is the recorded run; the other five are constructed.
Which check stops this run?
Six checks a script can run on a finished run, in order. For each of six runs, pick the check that stops it first; later checks are greyed out. If a run passes all six, pick the last button. The first run is recorded, the run that fixed nothing (the walkthrough run); the rest are constructed.
Recorded, the run that fixed nothing (the walkthrough run): exit code 0, result subtype success, is_error false, one entry in permission_denials. No file changed and the visible test still failed.
- Exit code is 0The process exit code, read from the shell after the run ends, is 0.pass
- Result subtype is successThe last line is the result line, and its subtype says success.pass
- is_error is falseThe same result line says is_error false.pass
- No permission was deniedThe result line's permission_denials list is empty, and no permission_denied line is in the stream.REFUSED
- The final message says the work is doneThe agent's last message reports that the job is finished and says what it changed.never reached
- You checked the work yourselfAfter the run, you ran the tests, or the real check for the job, and they pass.never reached
Constructed: the process ends with a non-zero exit code.
- Exit code is 0The process exit code, read from the shell after the run ends, is 0.REFUSED
- Result subtype is successThe last line is the result line, and its subtype says success.never reached
- is_error is falseThe same result line says is_error false.never reached
- No permission was deniedThe result line's permission_denials list is empty, and no permission_denied line is in the stream.never reached
- The final message says the work is doneThe agent's last message reports that the job is finished and says what it changed.never reached
- You checked the work yourselfAfter the run, you ran the tests, or the real check for the job, and they pass.never reached
Constructed: the exit code is 0, but the result line's subtype is not success.
- Exit code is 0The process exit code, read from the shell after the run ends, is 0.pass
- Result subtype is successThe last line is the result line, and its subtype says success.REFUSED
- is_error is falseThe same result line says is_error false.never reached
- No permission was deniedThe result line's permission_denials list is empty, and no permission_denied line is in the stream.never reached
- The final message says the work is doneThe agent's last message reports that the job is finished and says what it changed.never reached
- You checked the work yourselfAfter the run, you ran the tests, or the real check for the job, and they pass.never reached
Constructed: exit code 0, success, is_error false, no denial. The final message says the agent could not find the file and stopped.
- Exit code is 0The process exit code, read from the shell after the run ends, is 0.pass
- Result subtype is successThe last line is the result line, and its subtype says success.pass
- is_error is falseThe same result line says is_error false.pass
- No permission was deniedThe result line's permission_denials list is empty, and no permission_denied line is in the stream.pass
- The final message says the work is doneThe agent's last message reports that the job is finished and says what it changed.REFUSED
- You checked the work yourselfAfter the run, you ran the tests, or the real check for the job, and they pass.never reached
Constructed: exit code 0, success, is_error false, no denial. The final message says done, all tests pass. No test ran after the last edit.
- Exit code is 0The process exit code, read from the shell after the run ends, is 0.pass
- Result subtype is successThe last line is the result line, and its subtype says success.pass
- is_error is falseThe same result line says is_error false.pass
- No permission was deniedThe result line's permission_denials list is empty, and no permission_denied line is in the stream.pass
- The final message says the work is doneThe agent's last message reports that the job is finished and says what it changed.pass
- You checked the work yourselfAfter the run, you ran the tests, or the real check for the job, and they pass.REFUSED
Constructed: exit code 0, success, is_error false, no denial, the final message names the change, and you ran the tests yourself afterwards. They pass.
- Exit code is 0The process exit code, read from the shell after the run ends, is 0.pass
- Result subtype is successThe last line is the result line, and its subtype says success.pass
- is_error is falseThe same result line says is_error false.pass
- No permission was deniedThe result line's permission_denials list is empty, and no permission_denied line is in the stream.pass
- The final message says the work is doneThe agent's last message reports that the job is finished and says what it changed.pass
- You checked the work yourselfAfter the run, you ran the tests, or the real check for the job, and they pass.pass
Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.
MeasuredQuoted What is recorded and what is constructed
- Measured
- Six runs: the run that fixed nothing (the walkthrough run) is recorded; 5 are constructed. Constructed for this drill. Not a recorded run. Across all 247 runs every exit code was 0 and every result said success.
- Method
- A run stops at the first check it fails. The checks are our checklist. The vendor documents non-zero exit codes; no recorded run produced one.
- n
- 6 runs. 247 recorded
claude -pruns, one at a time on one Windows PC, Claude Code2.1.284, subscription login (no API key); no new run was made. No interval is claimed. - Source
- The walkthrough stream; the run records; Run Claude Code programmatically (saved 2026-10-10).
- Excludes
- A recorded failing exit: none in 247 runs. Other setups. Whether six checks suffice for your job.
Cost estimates and usage guards
A headless run gives you two readings that are easy to mix up: a cost estimate the CLI prints, and a usage meter your plan keeps. This module takes the estimate first, then the meter, then a guard that reads it.
The cost figure is an estimate
The result line carries total_cost_usd. The CLI works it out on its own by pricing the run's tokens at list price. That makes it nominal: what those tokens would cost at the published price, not a bill. The docs call such figures client-side estimates that "can differ from your actual bill." They also say that with --output-format json "the response payload includes total_cost_usd and a per-model cost breakdown". That format was not run here. The result line of the recorded stream-json runs carried the same field.
List price, not spendCalculatedn = 247 runs The sum, median and largest of the CLI's total_cost_usd. Each run was recomputed from its token counts and the vendor's published list prices (from its pricing page, saved earlier). That checks the CLI's arithmetic, not any bill. One Windows PC, Claude Code 2.1.284, subscription login (no API key); no new run.
The runs used a subscription login and costBasis was list in all 247, so this page makes no claim about what any account paid. The drill below gives eight sentences about these figures. Decide which ones hold.
Is this sentence true?
Eight sentences about the cost figures in the recorded runs. Read PASS (the button says ship) as: true as written. Read FAIL (send back) as: false, or claims more than the figures show. Every dollar figure is the CLI's estimate at list price (nominal), not spend. The sentences are constructed; the figures are recorded.
The 247 estimates add up to about seven dollars of spend.
The 247 estimates add up to an estimate of $6.94 at list price.
Recomputing from the token counts and the published list prices matched the CLI's own figure, to within one millionth of a dollar, in 247 of 247 runs.
The flag --max-budget-usd stops a run at exactly the cap.
--max-budget-usd.The walkthrough run's total_cost_usd, $0.0202, is what that run cost.
The median run's estimate was $0.0243 and the largest run's was $0.1202, both at list price.
Because the runs used a subscription login, the estimates tell you how much of the plan they used.
The cap in --max-budget-usd is checked against the CLI's own client-side estimate, not against your bill.
Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.
MeasuredCalculatedList price, not spend Where each figure comes from
- Measured
- Eight constructed sentences. Constructed for this drill. Not a recorded run. Their figures are recorded or calculated over the 247 results, and every dollar figure is the CLI's estimate at list price (nominal), not spend.
- Method
- FAIL: the sentence calls an estimate spend or cost, or claims precision the vendor says is not there. Recomputed from token counts and published list prices, to within one millionth of a dollar.
- n
- 8 sentences. 247 recorded
claude -pruns, one at a time on one Windows PC, Claude Code2.1.284, subscription login (no API key); no new run was made. No interval is claimed. - Source
- The run records; CLI reference and Run Claude Code programmatically (saved 2026-10-10).
- Excludes
- What any run was charged. Any plan meter.
Caps: documented, not run here
--max-budget-usd: "Stop the run once estimated spend on API calls reaches this amount (print mode only)." The page adds that spend can pass the cap, so you should leave headroom, and that the cap is checked against the same client-side estimate.--max-turns: "Exits with an error when the limit is reached. No limit by default." A turn is one round of the model answering. The installed help does not list this flag: it appears 0 times in both saved help outputs.
Quotedn = 0 recorded runs used either flag From the CLI reference, saved 2026-10-10. Documented, not run here.
The usage meter
Cost is the tokens priced at list. The meter is separate: how much of your plan's allowance a rate-limit window has used. A rate-limit window is a period, such as five hours or seven days, in which your plan allows a share of usage. Every recorded stream carried lines about it: 250 rate_limit_event lines across the 247 streams, 1 to 3 per stream. In 249 of the 250, status was allowed and rateLimitType was five_hour.
One line differed: status allowed_warning on the seven_day window, with a surpassedThreshold field. The harness script that made the runs read that line and stopped. It was started again later. This is the start of its own log line:
STOPPED: usage limit signal: status 'allowed_warning', type 'seven_day'
I publish no usage figure or reset time from that account. The script checked seven things after every run: an API key in use, fast mode on, overage in use (two settings that were off in all 247 runs), a rate-limit status other than allowed, an API error status, a missing result line, and a result that said is_error. It stopped at the first one and never retried. It also refused to start in two cases: inside a Claude Code session, with the message "this window is inside a Claude Code session (CLAUDECODE is set). Open a normal PowerShell window and run the script there.", and when an API-key variable was set, because its message says that can route runs to paid API usage. The docs agree that in -p mode "the key is always used when present".
MeasuredQuotedn = 250 rate-limit lines Counted in the 247 saved streams. The log line and the start message are the harness script's own words. One line is one warning, so no rate is claimed.
A usage guard
A usage guard is a few lines of script that read the meter between jobs and stop before a limit. It matters most for a scheduler (cron, a service that runs a script at set times) or CI (a service that runs your scripts on every code change), because nobody is watching either. The guard script we run between build steps works like this:
- It stops when the weekly reading is at or above a line, 75 percent by default.
- A reading more than 90 minutes old counts as no reading. So does a missing reading, or one from a window that has already reset.
- It exits with 0 to continue, 10 to stop, 11 for no reading and 12 for bad input.
- A guard that cannot see the meter does not assume it is fine.
Measuredn = 23 proof cases The script passed all 23, covering the paths that make no model call. They ran against the script, not against live meters. The drill below uses constructed readings only.
What would the usage guard do?
A usage guard is a few lines of script that read the meter between jobs and stop before a limit. This one follows the rules of the guard script we run between build steps: STOP at or above the line (75 percent by default); NO READING when there is no reading, it is more than 90 minutes old, or its window has reset. A reading that cannot be trusted is not treated as fine. Every reading here is constructed; none is from any account.
Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.
MeasuredReported What is measured and what is constructed
- Measured
- The guard script's rules: stop at 75 percent by default; no reading when none exists, it is over 90 minutes old or its window has reset; exit codes 0, 10 and 11. It passed 23 proof cases, covering the paths that make no model call. The readings are constructed. Constructed for this drill. Not a recorded run.
- Method
- The dial shows the margin over the line in points, negative meaning under, and is hidden in the NO READING band. BAD INPUT (exit code 12) is not modelled.
- n
- 1 script, 23 proof cases. No usage figure from the recorded account is published.
- Source
- The guard script's source and proof run, read 2026-10-10. Real readings come from the rate-limit lines in all 247 recorded streams.
- Excludes
- Any real reading. Whether 75 percent suits your plan. The five-hour window (reported, not acted on).
The startup cost of a call
Every claude -p call is a new process, and it has to start before it can work. In the 247 recorded runs the first line of the stream, system/init, arrived a median of 5.82 seconds after the process started. The fastest took 4.78 seconds and the slowest 8.17.
duration_ms1.2 smedian of the same threeCalculatedn = 247 runs, and 3 probes Timed by the harness on one Windows PC. The probe prompt was "Reply with exactly: OK". The last tile is the harness's wall time minus the result line's duration_ms: time spent outside the run's own measured time. Every run passed --safe-mode, so these are start times with customizations off, and yours will differ.
Each call pays that cost again. A job that reviews twelve files in twelve calls waits for twelve starts; one call that covers all twelve waits for one. Whether one prompt can do the whole job depends on the job, and I did not test whether a batched answer is as good. The drill does the arithmetic with your own start time.
How much time goes on call overhead?
A job reviews 12 files. How you split it into calls decides how many calls pay the overhead. Pick a split, the time one call spends outside its own measured time, and how many times the job runs; the readout is the total. The default, 6.16 seconds, is the median over the 247 recorded runs on one Windows PC of each run's wall time minus its result line's own measured duration: start-up and wind-down, outside the work. Replace it with your own: yours will differ.
Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.
CalculatedMeasured How the readout is calculated
- Measured
- Default 6.16 seconds: the median over 247 runs of wall time minus the result line's own measured duration, so it includes start-up and some wind-down. The first line of a stream arrived a median 5.82 seconds after the process started (4.78 fastest, 8.17 slowest); the one-line probe took a median 7.42 seconds, 1.2 of them the result line's own measured duration (3 probes).
- Method
- Calculated: calls x time per call x runs. The 12 files and five splits are constructed. It counts overhead only; whether a batched answer is as good was not tested.
- n
- 247 runs, 3 probes. 247 recorded
claude -pruns, one at a time on one Windows PC, Claude Code2.1.284, subscription login (no API key); no new run was made. Per call, one PC. - Source
- The run records; Run Claude Code programmatically (bare mode: documented as reducing startup time, not run here).
- Excludes
- Your own overhead: measure it. Bare mode. Other machines or days.
Bare mode: documented, not run here
The docs offer a faster start for scripts: "Add --bare to reduce startup time by skipping auto-discovery of hooks, skills, custom commands, subagents, installed plugins, MCP servers, auto memory, and CLAUDE.md." They call it "the recommended mode for scripted and SDK calls" that "will become the default for -p in a future release". They also say to set an API key "because bare mode doesn't use your subscription login".
All 247 recorded runs used a subscription login and none passed --bare, so nothing about it was measured, its start time included. The runs used --safe-mode instead. The docs describe it as "Start with all customizations disabled to troubleshoot a broken configuration" and say "Authentication, model selection, built-in tools, and permissions work normally", unlike --bare.
Quotedn = 0 recorded runs used --bare From the headless page and the CLI reference, saved 2026-10-10. The docs describe versions up to 2.1.296; the runs used 2.1.284.
Practice kit
The kit is two programs you can run yourself: a checker for a saved stream, and a wrapper that runs claude -p and then the checker. The block below carries the downloads, the checks, an example of each command and what the checker said about the 247 recorded streams.
Downloads need JavaScript and this block's script file.
What the checker looks at
check_stream.py reads one saved stream and prints PASS, FAIL, WARN or INFO for each of eight checks.
- The stream starts with
system/initand ends withresult. - The
resultline sayssubtypesuccess andis_errorfalse. terminal_reasoniscompleted.- Nothing was denied: no
permission_deniedline, and an emptypermission_denials. A denied action did not run, so the work may be undone. - The model. With
--expect-model, the init line, every assistant line and everymodelUsagekey must name that model. Without it, the names are only printed. - With
--require-subscription,apiKeySourceisnone. - The last rate-limit line says
allowed. Only the status word is read; no usage figure or reset time is printed. - The CLI's cost estimate, printed and labelled an estimate, not a bill.
A WARN means a check could not be made. That is no reading, not a good reading. It changes the exit code only with --strict.
How to run them
python check_stream.py run.jsonl --expect-model <model name> --require-subscription
python run_headless.py --prompt-file prompt.txt --out-dir runs -- --model <model name> --tools Read --permission-prompts none
The first checks a stream you saved. The second runs claude -p once with your flags after the --: --tools Read limits the run to the Read tool, and --permission-prompts none denies anything that would ask a person. It saves what it printed, then runs the first on it.
The wrapper has only run against a stub. I tested run_headless.py against stub programs that replay a recorded stream. I never ran it against the real claude program or on macOS or Linux. Try it first on a harmless prompt.
What it said about the recorded streams
I ran check_stream.py once on each of the 247 saved streams of the recorded runs (one Windows PC, Claude Code 2.1.284, a subscription login), with --require-subscription and --expect-model set to each run's own model name. I made no new run.
| Check | What it asks | Streams it failed, of 247 |
|---|---|---|
| a | First line system/init, last line result | 0 |
| b | subtype success and is_error false | 0 |
| c | terminal_reason completed | 0 |
| d | No denied action | 39 |
| e | Every model name equals the run's own --model | 0 |
| f | apiKeySource is none | 0 |
| g | The last rate-limit line says allowed | 1 |
Measuredn = 247 recorded streams Streams each check failed, read from the saved files by a program we wrote later.
Check d failed on the 39 streams in which a command was denied. Check g failed on the one stream whose only rate-limit line said allowed_warning. Claude Code itself exited 0 on all of these runs, so a script that read only its exit code would have passed them. The run that fixed nothing is one of the 39, because it had one denial. The checker fails it for the denial and not because it looked at the repository. A run that fixed nothing and had no denial would pass every check here, so this checker does not replace running the tests.
Measured How the checker was tested
- Measured
- How many of the 247 saved streams each check failed, and whether the checker and wrapper do what the page says when their input is damaged.
- Method
- One checker process per stream, with that run's own model name and
--require-subscription. Every outcome was compared with a separately written second reading of the same file and with the run's saved record. Copies of one recorded stream were then damaged one way at a time (last line dropped, model name changed, denial added, error flag set, API-key source flipped, last rate-limit status set to rejected, empty file, a line that is not JSON); the check named for that damage had to fail, or the checker had to refuse the file. The wrapper ran only against stub programs. Python3.14,3.10and3.11printed the same. - n
- 247 saved streams, one checker run on each. No confidence interval is claimed.
- Source
- The saved streams and run records of the 247 recorded runs (one Windows PC, Claude Code
2.1.284, a subscription login). No new run was made. - Excludes
- Streams from any other setup. A wrong fix that ran without a denial. The real
claudeprogram, which the wrapper never met. macOS, Linux, Python 3.9 (only parsed, not run), and the wrapper on any Python but3.14.
Before the first unattended run
Settle where the run starts. The folder a run starts in is its run folder, also called its working directory. The docs say "A -p session shows no workspace trust dialog and no per-server approval prompt." They also say that without --bare it runs the hooks in a project's settings file and connects the servers in its MCP file, even in a folder you have not trusted. So run an unattended job in a folder you control.
The recorded runs also passed --no-session-persistence, which the docs describe as "Disable session persistence so sessions are not saved to disk and cannot be resumed." The 75 bug-fix runs passed --restricted: "Use it when an evaluation harness drives claude on a shared machine and Claude Code must not run commands or read that machine's user and project settings." Those runs named Bash in --tools, which the docs say keeps it available. Give your own job a time limit, because a -p run can stay open to wait for background work, and the default cap is 10 minutes of continuous idle waiting. Switches that turn the permission checks off, such as --dangerously-skip-permissions and the bypassPermissions mode, were not used in any recorded run, and this page does not recommend them.
Quotedn = 247 recorded runs Sentences from the headless page and the CLI reference, saved 2026-10-10. Documented, not run here. The harness script refused to start inside a Claude Code session (module six).
The drill asks four questions and lists the steps that apply to you.
Before the first unattended run
Four questions; steps that do not apply disappear. Each step names its source: our harness script, which ran the 247 recorded runs, or a saved vendor page. Nothing here was run on macOS or Linux, and we did not run the practice kit's wrapper against the real CLI.
- Open a normal terminal window firstOur harness script refused to start inside a Claude Code session. Its message said:
this window is inside a Claude Code session (CLAUDECODE is set). Open a normal PowerShell window and run the script there.Do the same: open your own terminal and start the job there. The practice kit's wrapper refuses too. (Source: our harness script.) - Decide on purpose about the API-key variableOur recorded runs used the subscription login (
apiKeySourcesaidnonein 247 of 247 runs), and our harness stopped if an API key was in use. The vendor's authentication page says: "In non-interactive mode (-p), the key is always used when present." So a key left in the environment is used by a headless run. Unset it (unset ANTHROPIC_API_KEYfalls back to the subscription) or decide on purpose. The practice kit's wrapper refuses to start while the variable is set. (Sources: our harness, saved Authentication page.) - Check your version and the flags you plan to useRun
claude --versionandclaude --help. The 13 vendor pages saved for this class mention versions up to2.1.296; the runs used2.1.284. Of the 74 flags in the CLI reference, 15 were not in that help,--max-turnsamong them (0 mentions in the saved help). Documented is not tested: check each flag in your own help. (Sources: saved CLI reference, saved help output.) - Update before you rely on the permission-prompts flagThe vendor says
--permission-promptsneeds2.1.259or later; earlier versions reject it with an unknown-option error. The runs used2.1.284. (Source: saved headless page.) - Give the job a folder you controlWithout
--bare, a-psession runs the hooks in a project's settings file and connects the servers in its MCP file, even in a folder you have not trusted, with no trust dialog. Our runs passed--safe-modeand--strict-mcp-config. Use an empty or known folder. (Sources: saved headless page, CLI reference.) - Turn permission prompts off and say what may runNobody is there to answer: pass
--permission-prompts none. The vendor's headless page says to when nobody can answer, for example in a scheduled job; anything that would need approval and is not covered by your allow rules is denied, and the run continues. Write the allow rules in the job spec above. (Source: saved headless page.) - Read the vendor's CI pages before you copy anythingThe saved GitHub Actions and GitLab pages describe running Claude Code in a pipeline; we ran neither. The vendor calls
--barethe recommended mode for scripted calls, but bare mode does not use your subscription login, so it needs an API key. We ran no bare run. (Sources: saved CI and headless pages.) - Set a time limitPut a timeout around the process in your scheduler or wrapper. Our harness had a limit and stopped none of the 247 runs for passing it. Stopping a run with SIGTERM makes Claude Code exit with code
143and record no result for the unfinished turn, the vendor documents; we did not test that. (Sources: our harness, saved headless page.) - Save the stream, stderr and the exit code of every runWrite the stream to a file, keep stderr apart, and record the exit code. Our harness kept all three for each of the 247 runs, which is how this class counted them. The exit code alone would not have shown the 39 runs with a denial. (Source: our harness script.)
Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.
MeasuredQuotedn = 247 recorded runs Counts measured from 247 recorded claude -p runs, one at a time on one Windows PC, Claude Code 2.1.284, subscription login (no API key); no new run was made. Each step names its source. The vendor's behaviour is documented, not run here, and the kit wrapper was not run against the real CLI.
Where to go next
- Class 98: AI Bug Fix reads the same 75 bug-fix runs as a debugging loop, through another lens.
- Class 96 is the model comparison, built from the same recorded runs. This class does not compare models.
- Class 71: Evals and Testing AI Output is about checking the work itself, as a system.
- Class 101: Claude Code Cost is next on the path. It reads what runs like these come to: tokens by type, and an estimate against a bill.
One more class is planned, with no date and no promise that it will appear: What Your Coding Agent Is Allowed to Run.
What we did not verify
This is the page's own list of gaps. I wrote it from saved runs and made no new run, so each item is a limit on those files or on what we tested.
- No new run was made. Every figure comes from 247 saved runs, recorded one at a time earlier.
- One PC, one CLI version. The runs came from one Windows PC with Claude Code
2.1.284and a subscription login, while the 13 saved docs pages describe versions up to2.1.296. - No non-zero exit was recorded. What a failing run prints and exits with is documented here, not seen.
- Several flags were not run. That covers
--output-format json,--json-schema,--max-turns,--max-budget-usd,--bare,--resume,--continueand--input-format. - Other permission modes were not run. No recorded run passed
--permission-mode, sodontAskandbypassPermissionsamong its values were not tried. - SIGTERM and exit code 143 were not tried. The 10MB input cap and the 10-minute background wait are documented only.
- The installed help does not list
--max-turns. The docs describe it, so I cannot say whether your version accepts it. - The wrapper only met a stub. We did not run
run_headless.pyagainst the realclaudeprogram. - The checker read streams from one harness. It cannot see a wrong fix that ran without a denial, so it does not replace running the tests.
- macOS, Linux and CI services were not run. The kit was tested on Windows only. The counts above come from Python
3.14; the checker also printed the same on3.10and3.11, and3.9was only parsed, not run. - Cost figures are estimates. I read no bill, so nothing here says what any account paid.
- The usage guard met no live meter on this page. The drill's readings are constructed, and the 23 proof cases ran against the script.
- The drills' constructed cases are not recorded runs. Each drill says which of its cases are recorded.
- The docs may have changed since 2026-10-10, when the pages were saved.
- Your plan's rules for headless use were not tested. Check them for your kind of job.
- The walkthrough is one run. It shows one way a run can go. It does not show how often that happens.
Bottom line
Run it with nobody watching, then read the stream, check the result line and the denials, read the model back, and check the work itself. In 247 recorded claude -p runs on one Windows PC (Claude Code 2.1.284, a subscription login, one at a time) every exit code was 0, stderr was empty and every result line said success, even in the 39 runs where a command was refused and the one bug-fix run that left its repository unchanged. The model named in the stream matched the flag in 247 of 247 runs, and the first line took a median of 5.82 seconds to arrive. This page shows no failing exit, no run of --bare, --max-turns, --max-budget-usd or JSON output, and no run of the wrapper against the real program; I wrote it from saved runs with no new run made.
Questions
How do I run Claude Code headless?
Add the -p flag: claude -p reads a prompt, does the work with nobody at the keyboard, prints what happened and exits. The 247 recorded runs on this page also passed --output-format stream-json with --verbose, a --tools list and --permission-prompts none, and took the prompt on standard input. One Windows PC, Claude Code 2.1.284, a subscription login; no new run was made. Check each flag against your own version.
What does claude -p do?
It is print mode. Claude Code takes your prompt, does the work as one fresh process, writes the result to standard output and exits with a code. With --output-format stream-json each step of the run is one JSON line, from a system/init line first to a result line last. That held for all 247 recorded streams, on one Windows PC with Claude Code 2.1.284.
Does exit code 0 mean the headless job worked?
Not by itself. It means the process finished. All 247 recorded runs exited 0 with an empty stderr and a success result line, including 39 runs in which Claude Code refused a command and the one bug-fix run that left its repository unchanged. Read the result line and the denials, then check the work itself, for example by running the tests. The docs describe non-zero exits; none was recorded here.
How do I know which model answered a headless run?
Read it from the stream instead of trusting the flag. Four places name the model: the system/init line, every assistant line, the result line's modelUsage key and its canonicalModel field. In the 247 recorded runs all four matched the --model value, and every run passed a full model name, not an alias. Log those names next to each run's result.
How do I stop a headless run asking for permission?
Nobody can answer, so say what may run. Pass --permission-prompts none, which the docs say needs Claude Code 2.1.259 or later, and list allow rules such as Bash(python -m pytest *). The docs add that in a -p run with no host these requests are denied either way. All 247 recorded runs passed the flag, so they cannot show what it changes. Switching the checks off was used in none, and this page does not recommend it.
Why was a command denied in a headless run?
A command that needs approval and no allow rule covers is denied at once when nobody can answer; plain read-only commands ran without a rule. The recorded runs had 41 denials in 39 runs, all on Bash, each with the same reason: no approval surface in this session. A rule has to match each part of a chained command, so one unapproved part such as sed -i refused the whole line. Denials show in the stream and in permission_denials.
Does a headless Claude Code run cost money?
The CLI prints total_cost_usd, its own estimate at list price. Across the 247 recorded runs the estimates added up to 6.94 dollars, nominal, with a median of 0.0243 and a largest of 0.1202 dollars per run. The docs say such estimates can differ from your actual bill, so this page makes no claim about what anyone paid. The runs used a subscription login and no API key.
How do I keep a scheduled job from running into a usage limit?
Read the meter between jobs and stop before the limit. All 247 recorded streams carried rate-limit lines, and one carried an allowed_warning on the seven-day window, which stopped the harness script. A guard should treat a missing, stale or already-reset reading as no reading, not as fine. The guard script we run between build steps stops at 75 percent by default and counts a reading over 90 minutes old as no reading.
Why did the recorded runs take about six seconds to print a first line?
Each recorded call was a new process that had to start before the model worked. In the 247 recorded runs the first stream line arrived a median of 5.82 seconds after the process started (fastest 4.78, slowest 8.17) with --safe-mode on, on one Windows PC. A one-line probe took a median of 7.42 seconds in all, 1.2 of them the result line's duration_ms. Where one prompt can cover the job, fewer calls pay fewer starts.
Does --bare work with a subscription login?
The docs say no: bare mode doesn't use your subscription login, so it needs other credentials, such as an API key. They say --bare skips auto-discovery of hooks, skills, MCP servers and CLAUDE.md so scripted calls start faster. That is documented, not run here: no recorded run used --bare, and all 247 used a subscription login with --safe-mode instead.
What is stream-json?
It is an --output-format value that prints newline-delimited JSON, one object per line, as the run goes. The 247 recorded streams held 13,033 lines of nine kinds. Every stream began with a system/init line and ended with a result line. The other values, json and text, are documented, not run here.
Is the class free, and do I need to sign up?
Yes, it is free. You do not need to sign up or give an email address. The practice kit downloads (a stream checker, a wrapper, a README and a license notice) are built in your browser from the page, with no server involved. The wrapper has only run against a stub, not the real claude program.