Skip to content

Troubleshooting ​

Each problem is listed by what Gadfly prints. A refusal prints what the last line of a Python traceback would, its class, which says what to fix and gives the exit status, and what went wrong, then on the next line what to do; a warning, for what a run did not do as asked though it went on, prints the same way. With --json a refusal is one object on standard error and a warning sits under warnings in the run's result, as each table below can show on the site.


Exit statuses ​

The status says what happened without a word read, and the first line of a refusal says it in words, so a script, a person and another model read it alike.

ExitTypeWhat it means
0nonedone
1OpeningWarningevery call came back with no answer, so nothing was read
2UsageErrorthe command or the call asks for what cannot be done, and nothing was spent
3StoppedWarningthe run stopped before its plan's end, so its records are partial; --kept finishes them
4FrameErrorthe frame is refused
5RecordsErrorrecords or a report are not this frame's run
6BatteryErrorthe battery cannot be made, set up or reached
7FileNotFoundError, FileExistsError, PermissionError, IsADirectoryError, NotADirectoryErrora file or a folder cannot be read or written
70any othera bug in Gadfly, which prints its traceback
130KeyboardInterruptstopped by Ctrl-C
141nonethe program reading its output stopped reading (| head), so the command ended quietly, as any Unix tool does

UsageError ​

The command or the call asks for what cannot be done, and nothing was spent. When Gadfly prints what to do, it opens the second column.

You seeWhat to do
UsageError: the plan needs 40 calls; the budget allows 10gadfly plan shows the calls a whole run needs. --calls is below the plan, so no file was made.
UsageError: unrecognized arguments --stopgadfly run --help shows its arguments. Flags are named in full, since a shortened one would change meaning when a longer one is added.
UsageError: --battery is module:Class (for example midwife.batteries.egemma:EGemma)Name the class after a colon.
UsageError: the plan has no trial 'controll'gadfly plan lists its trials. Name one of them with --only.
UsageError: battery 'egemma' does not take calls at onceLeave out --parallel, as this battery takes one call at a time.
UsageError: - reads standard input, which is a terminalpipe the file in, or name it.
UsageError: standard input is read oncename - for one file at most.
UsageError: --json and --out - both print to standard outputchoose a file for --out.
UsageError: the token budget (0) is spent--token-budget must cover one call's --output-tokens. Nothing was spent and no file made.

FrameError ​

The frame is refused as written, and Gadfly never repairs it. gadfly check-frame prints each refusal as a FrameError and where it is, without a call, and Write a frame lists the common ones.

You seeWhat to do
FrameError: file 'frame.json' is not JSON at line 2, column 1 (Expecting value)Correct the file at that place; a frame is plain JSON.
FrameError: file 'frame.json' is not UTF-8 textSave the file as UTF-8.
FrameError: the body has no span 'r9'anchors name spans of the body. Name as anchors only the ids of its spans.
FrameError: required influence is tested by ablation, and none is declaredset ablation to true.

RecordsError ​

The records, or a report, are not what this frame's run made, so no verdict is read from them.

You seeWhat to do
RecordsError: a record belongs to another frameThe records were made under another frame: another file, or this one edited after the run, since any change makes a new hash. Read them with the frame they were made under.
RecordsError: a record of trial 'baseline' was made on another bodyThe trial's text differs from the one the record was made on. The frame, or the way Gadfly plans it, changed after the run.
RecordsError: a record is a JSON object or RecordsError: a record's trial is not a strA line of the records file is not a record Gadfly wrote: another file, or one edited by hand.
RecordsError: file 'records.jsonl' is not JSON at line 3, column 1 (Expecting value)That line is not a record; the file was cut short or edited.
RecordsError: a record's usage counts are not whole numbersA run keeps usage as whole counts by name, so something else wrote this record.
RecordsError: a record's served is not a strThe same for what the battery said it served.
RecordsError: file 'records.jsonl' is not UTF-8 textThe file was written by another tool or edited in another encoding. Save it as UTF-8.
RecordsError: two records for input 'pr', trial 'baseline', run 2The same call is kept twice, usually from two files joined into one. Keep one of them.
RecordsError: a kept record of trial 'baseline' was made from another promptA record passed as --kept was made with a prompt this frame does not plan for that call. Resume only from the records this frame's own run made.
RecordsError: file 'kept.jsonl' holds a lone surrogate at line 3no file can hold it; correct or drop that record, then run again. That record holds text, often from another tool, that cannot be written back, and nothing was spent.
RecordsError: a portfolio reads `gadfly report --json` documents, and this is not oneEach file portfolio reads is the output of gadfly report --json.

BatteryError ​

The battery cannot be made, cannot serve this frame as it is set up, or cannot reach what it needs, and no call was made.

You seeWhat to do
BatteryError: no eGemma keyname a file that holds it in the environment variable EGEMMA_API_KEY_FILE, or set EGEMMA_API_KEY.
BatteryError: file '/Users/you/.egemma/key' is emptythe environment variable EGEMMA_API_KEY_FILE names it; write eGemma's key into it.
BatteryError: the router takes no data policyupdate the router, so that each call reaches only endpoints that keep no data. The unified battery sends each call under the router's deny data policy, and the router's GET /health does not list it.
BatteryError: the router at 'http://127.0.0.1:18766/health' could not be asked what it takesstart the router, or install it. The unified battery asks the router, before any call, whether it takes the deny data policy.
BatteryError: the router takes no endpoint pinsupdate the router, or leave battery.endpoints out. The unified battery pins a frame's endpoints through the router's own header, and the router's GET /health does not list it.
BatteryError: battery.endpoints lists 1 to 16 endpoint tagseach a tag the router knows, with no comma or space around it.
BatteryError: no router tokenset the environment variable CODEX_ROUTER_TOKEN, or install the router. The unified battery found no token in the variable or in the router's own settings file.
BatteryError: this frame asks for battery 'egemma', not 'unified'A frame names its battery in battery.kind, and another battery is another frame. Pass the one it names.
BatteryError: 'midwife.frame:parse' is not a batterya battery has a name, check() and observe(). The name after the colon is found, but it is not a battery class or object.
BatteryError: module 'tests.fixtures.refund' has no 'NoSuchReader'The module was found, but not the class named after the colon. Check its spelling and case.
BatteryError: module 'mine' could not be importedit said "No module named 'mine'". The battery's module is imported from the current directory. Run from the folder that holds it, or check its name.
BatteryError: the evaluator's address must not hold a user or passwordA battery's address names a user or a password. Gadfly refuses it without repeating it; take the credentials out of the address.
BatteryError: the evaluator's address 'http://127.0.0.1:99999' has no valid porta port is a number from 0 to 65535.
BatteryError: battery.timeout is a number of seconds, up to 3600The local-json battery's whole-call deadline is missing a positive number, or asks for more than an hour.
BatteryError: battery.reasoning is true or falseThe egemma battery reads "reasoning": true to let Gemma think first; Gemma has no levels.
BatteryError: battery.reasoning is one of none, minimal, low, medium, high, xhigh, maxThe unified battery passes battery.reasoning to the model as an effort, one of these words.

File errors ​

A file or a folder the command names cannot be read or written, and nothing was spent. These are Python's own errors, said in plain words.

You seeWhat to do
FileNotFoundError: file 'frame.json' does not existCheck the path, or the file that the environment variable EGEMMA_API_KEY_FILE names.
FileNotFoundError: folder 'runs' does not existmake it, or choose another --out.
PermissionError: folder 'runs' cannot be writtenchoose another --out. The folder is not yours to write.
IsADirectoryError: 'runs' is a folder, not a fileName the file in the folder, not the folder.
NotADirectoryError: path 'notes.txt/frame.json' runs through a file, not a folderA part of the path that should be a folder is a file. Check the path.
FileExistsError: file 'records.jsonl' exists alreadykept records are never written over; choose a new --out, or finish it with --kept. To finish a stopped run, pass its file as --kept with a new --out.

KeyboardInterrupt ​

Ctrl-C stops a run, and every record made so far is already in its file.

You seeWhat to do
KeyboardInterrupt: stopped by Ctrl-C after 12 calls; they are in file 'records.jsonl'finish with --kept 'records.jsonl' and a new --out. Only the missing calls are made, and the new file holds the whole run, to report as one. A crash leaves the file the same way.

OpeningWarning ​

Calls came back with no answer, as openings: each is kept, and reads as no answer. The run goes on, and run exits with 1 only when every call did.

You seeWhat to do
OpeningWarning: 10 of 10 calls came back as openings, most often 'eGemma could not be reached (ConnectionRefusedError)' (10 times)nothing was read; run again into a new file once it answers. Nothing answers at EGEMMA_URL, by default http://127.0.0.1:8000: start eGemma, or point the variable at it.
"the router could not be reached (...)" or "the evaluator could not be reached (...)"The same for the router, at http://127.0.0.1:18766, or for your evaluator, at the frame's battery.url.
"eGemma answered 401" or "the router answered 401"The key or the token is wrong.
"eGemma answered 503 after 3 retries"eGemma stayed busy or has no model loaded; the battery waits and retries before it gives up. Check eGemma's own log.
"the router refused model 'ultra-spark/deepseek-v4.1-flash-1017', whose provider trains on its inputs"The router's own list says this model's provider trains on its inputs, and the call went under the deny data policy. Choose another model, or name this one under trainsOnInputs to send it there.
"no endpoint that keeps no data serves this model (the router's provider setting); naming it under trainsOnInputs allows one that may train"No endpoint the router may use for that model keeps no data, and the frame does not consent to one that trains on its inputs. Choose another model, or pin endpoints with battery.endpoints.
"a cap of 64 output tokens leaves no room for a thought beside the answer's 64"With "reasoning": true, the egemma battery keeps 64 of each call's output tokens for the answer. Raise --output-tokens, to 4160 for a whole thought; no request was made.
"eGemma answered 422"eGemma refused the request as written. With "reasoning": true, an eGemma older than its reasoning change does not know the field: update eGemma.

StoppedWarning ​

The run stopped before its plan's end, so its records are partial, and run exits with 3. The records made are kept, and --kept finishes them.

You seeWhat to do
StoppedWarning: the run stopped after 3 of 8 calls; the token budget (30) is spentfinish with --kept 'records.jsonl' and a new --out. Raise --token-budget first.
StoppedWarning: the run stopped after 1 of 8 calls; the battery reported 900 output tokens for a call capped at 64finish with --kept 'records.jsonl' and a new --out. The battery spent more output tokens on one call than --output-tokens allows, and that call's record is kept. A battery that writes before it answers spends tokens on the whole call: set --output-tokens to what a whole call may spend.

Reading a verdict ​

A verdict that reads unresolved is a finding, not an error, and report exits with 0.

You seeWhat it means, and what to do
unresolved, with no agreed level in ...Fewer than agree answers matched in those trials: the reader split, or the calls came back as openings. The run's OpeningWarning says which.
unresolved, with only open checks beside itEvery trial agreed, but a check the category needs is still open. Reading a verdict explains carriage and each check.

Developing Gadfly ​

You seeWhat it means, and what to do
1 transcript(s), copies or references differA page quotes output the code no longer prints, or a copy, frame view or API section is stale. Run uv run python tools/doc_outputs.py --write.
tests/test_kept_runs.py failsA change to the verdict rules changed a kept run's category. That is a method change: say so in the docs, and never edit the kept runs.
npm run docs:build fails on a dead linkA link names a page that does not exist. test_every_link_lands finds it, with any heading anchor that does not.