Two defects in analysis/ghidra/benchmarks/corpus/record_baseline.py, both
found while harvesting the #1805-c ghidra-slot round (PR #2641). Filed together
because both are in the same file and both are about the request the harness
actually makes versus the one it claims to make.
1. No retry and no incremental save — one container restart cost 10 cells
ghidra-ollama-1 was recreated at 2026-08-27T20:18:17Z, mid-round. All
three benchmark lanes died within one second of each other:
20:18:16 done A qwen3:14b run2 exit:1 <- 53 min of work, discarded
20:18:17 done A qwen3:14b run3 exit:1 <- instant, container still restarting
20:18:17 done B qwen3:14b run1 exit:1 <- instant
20:18:17 done B qwen3:14b run2 exit:1 <- instant
identically for qwen2.5-coder:14b-instruct-q4_K_M (lane 2) and
Seneca-Cybersecurity-LLM-x-QwQ-32B (lane 3). The failure is
http.client.RemoteDisconnected out of urlopen(req, timeout=600) at
record_baseline.py:251, with no handler above it.
Two separate costs:
- No retry. The container was back within seconds — the very next run
(B run3, started 20:18:17) completed normally an hour later. Three of the
four failures per lane were runs that fired into a two-second restart window
and died instantly. A bounded retry with backoff on connection errors would
have lost nothing at all.
- No incremental save. The
A run2 failure had already scored 11 of 14
cases across 53 minutes. Every one of those answers was thrown away because
the report is only written at the end. Cases are independent; there is no
reason a run cannot checkpoint per case and resume.
Net effect: 10 of 72 cells lost, leaving three models at N=1 in a matrix
whose whole purpose was N>=3 with error bars, and directly blocking the
gemma-4-26B-A4B promotion call in #1805-c because the incumbent's Tier B
number is one of them.
Fix: catch connection-class exceptions in ask_model, retry with backoff (the
model is resident and keep_alive is generous, so a retry is cheap), and write
per-case results incrementally so an aborted run resumes instead of restarting.
The lane script's resume guard already keys on the output filename, so a
partial-report format is the missing half of a mechanism that already exists.
Related but distinct: #2032 records the same class of defect (no incremental
save, no HTTP retry) in claims.py. This is the same lesson in a second file —
worth checking whether any other benchmark entry point has it too.
2. context_tokens and keep_alive are recorded but never sent
Every report stamps a qualification_request block that is quoted verbatim as
the round's pins:
{"concurrency": 1, "context_tokens": 8192, "keep_alive": "10m",
"output_tokens": 512, "seed": 144, "temperature": 0, "thinking": false}
The payload ask_model actually builds carries model, messages,
temperature, max_tokens, seed, stream and (since #1953)
reasoning_effort. There is no num_ctx and no keep_alive anywhere in the
file — grep -n "num_ctx\|keep_alive\|context_tokens" returns nothing but
unrelated analysis_options hits.
On the analysis host the effect is benign and was checked rather than assumed:
the container sets OLLAMA_CONTEXT_LENGTH=32768 and the largest evidence in
the round is 4002 characters (~1000 tokens), so nothing was truncated — the
served context was four times the pin that was published.
It is still a false pin, and it is a hole in #1947 rule 5 (tag + digest +
request pinning), which exists precisely so a round can be reproduced. A report
that states a context length it did not set will silently produce different
numbers on a host with a different OLLAMA_CONTEXT_LENGTH, and nothing in the
artifact would show it.
Fix: send options.num_ctx and keep_alive for real, or drop them from
qualification_request and record the server-side context instead. Sending
them is better — the pin is the point. Either way the recorded block and the
transmitted request must agree, and a test should assert that they do.
Two defects in
analysis/ghidra/benchmarks/corpus/record_baseline.py, bothfound while harvesting the #1805-c ghidra-slot round (PR #2641). Filed together
because both are in the same file and both are about the request the harness
actually makes versus the one it claims to make.
1. No retry and no incremental save — one container restart cost 10 cells
ghidra-ollama-1was recreated at 2026-08-27T20:18:17Z, mid-round. Allthree benchmark lanes died within one second of each other:
identically for
qwen2.5-coder:14b-instruct-q4_K_M(lane 2) andSeneca-Cybersecurity-LLM-x-QwQ-32B(lane 3). The failure ishttp.client.RemoteDisconnectedout ofurlopen(req, timeout=600)atrecord_baseline.py:251, with no handler above it.Two separate costs:
(
B run3, started 20:18:17) completed normally an hour later. Three of thefour failures per lane were runs that fired into a two-second restart window
and died instantly. A bounded retry with backoff on connection errors would
have lost nothing at all.
A run2failure had already scored 11 of 14cases across 53 minutes. Every one of those answers was thrown away because
the report is only written at the end. Cases are independent; there is no
reason a run cannot checkpoint per case and resume.
Net effect: 10 of 72 cells lost, leaving three models at N=1 in a matrix
whose whole purpose was N>=3 with error bars, and directly blocking the
gemma-4-26B-A4Bpromotion call in #1805-c because the incumbent's Tier Bnumber is one of them.
Fix: catch connection-class exceptions in
ask_model, retry with backoff (themodel is resident and
keep_aliveis generous, so a retry is cheap), and writeper-case results incrementally so an aborted run resumes instead of restarting.
The lane script's resume guard already keys on the output filename, so a
partial-report format is the missing half of a mechanism that already exists.
Related but distinct: #2032 records the same class of defect (no incremental
save, no HTTP retry) in
claims.py. This is the same lesson in a second file —worth checking whether any other benchmark entry point has it too.
2.
context_tokensandkeep_aliveare recorded but never sentEvery report stamps a
qualification_requestblock that is quoted verbatim asthe round's pins:
{"concurrency": 1, "context_tokens": 8192, "keep_alive": "10m", "output_tokens": 512, "seed": 144, "temperature": 0, "thinking": false}The payload
ask_modelactually builds carriesmodel,messages,temperature,max_tokens,seed,streamand (since #1953)reasoning_effort. There is nonum_ctxand nokeep_aliveanywhere in thefile —
grep -n "num_ctx\|keep_alive\|context_tokens"returns nothing butunrelated
analysis_optionshits.On the analysis host the effect is benign and was checked rather than assumed:
the container sets
OLLAMA_CONTEXT_LENGTH=32768and the largest evidence inthe round is 4002 characters (~1000 tokens), so nothing was truncated — the
served context was four times the pin that was published.
It is still a false pin, and it is a hole in #1947 rule 5 (tag + digest +
request pinning), which exists precisely so a round can be reproduced. A report
that states a context length it did not set will silently produce different
numbers on a host with a different
OLLAMA_CONTEXT_LENGTH, and nothing in theartifact would show it.
Fix: send
options.num_ctxandkeep_alivefor real, or drop them fromqualification_requestand record the server-side context instead. Sendingthem is better — the pin is the point. Either way the recorded block and the
transmitted request must agree, and a test should assert that they do.