<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: pm25coder</title>
    <description>The latest articles on DEV Community by pm25coder (@pm25coder).</description>
    <link>https://dev.to/pm25coder</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4085808%2Fcb805875-7c79-4a08-89a7-d02442ff9382.png</url>
      <title>DEV Community: pm25coder</title>
      <link>https://dev.to/pm25coder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pm25coder"/>
    <language>en</language>
    <item>
      <title>Both URLs followed main, so my residual could mix two dates</title>
      <dc:creator>pm25coder</dc:creator>
      <pubDate>Fri, 06 Nov 2026 22:18:04 +0000</pubDate>
      <link>https://dev.to/pm25coder/both-urls-followed-main-so-my-residual-could-mix-two-dates-5c40</link>
      <guid>https://dev.to/pm25coder/both-urls-followed-main-so-my-residual-could-mix-two-dates-5c40</guid>
      <description>&lt;p&gt;A reader left a note under my last post that I have not been able to put down. He said, in effect: pin the revision before you fetch the config and the model metadata, because both of the URLs in your example follow a mutable head independently, and a nonzero residual can describe a mixed observation rather than a missing term in the formula.&lt;/p&gt;

&lt;p&gt;He is right, and it is not a theoretical race. The snippet he was reading asks two URLs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://huggingface.co/{repo}/raw/main/config.json
https://huggingface.co/api/models/{repo}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both follow &lt;code&gt;main&lt;/code&gt;. Two reads, two chances for the branch to move between them, and then a comparison between a number computed from one and a number computed from the other. The residual in that post was the gap between the parameter count a model card claims and the parameter count the checkpoint's own config implies. If the two reads landed on different commits, that gap could be describing a mix of two states instead of a property of one.&lt;/p&gt;

&lt;p&gt;So this week I measured what it costs to stop doing that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the snippet has two URLs at all
&lt;/h2&gt;

&lt;p&gt;I did not fetch two URLs because I like two URLs. The API response cannot answer "how many parameters does this checkpoint store", because the config object inside it does not carry the fields that answer it.&lt;/p&gt;

&lt;p&gt;Across seven repos, &lt;code&gt;api/models/{repo}&lt;/code&gt; returned a &lt;code&gt;config&lt;/code&gt; object with three keys — &lt;code&gt;architectures&lt;/code&gt;, &lt;code&gt;model_type&lt;/code&gt;, &lt;code&gt;tokenizer_config&lt;/code&gt; — and four for Phi-3-mini, which adds &lt;code&gt;auto_map&lt;/code&gt;. None of the seven carried &lt;code&gt;vocab_size&lt;/code&gt;, &lt;code&gt;num_hidden_layers&lt;/code&gt; or &lt;code&gt;hidden_size&lt;/code&gt;. Those three live in &lt;code&gt;config.json&lt;/code&gt;, and I could not reach them from the metadata call.&lt;/p&gt;

&lt;p&gt;So the shape is forced: one call for what the API knows, one call for the file the API does not inline. The only open question is which revision the second call names.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the two paths returned today
&lt;/h2&gt;

&lt;p&gt;Seven repos, four model families, all ungated, read in one sitting:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;repo&lt;/th&gt;
&lt;th&gt;sha (first 12)&lt;/th&gt;
&lt;th&gt;safetensors.total&lt;/th&gt;
&lt;th&gt;main vs pinned&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HuggingFaceTB/SmolLM2-135M-Instruct&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12fd25f77366&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;134,515,008&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HuggingFaceTB/SmolLM2-360M-Instruct&lt;/td&gt;
&lt;td&gt;&lt;code&gt;a10cc1512eab&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;361,821,120&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HuggingFaceTB/SmolLM2-1.7B-Instruct&lt;/td&gt;
&lt;td&gt;&lt;code&gt;31b70e2e869a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1,711,376,384&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen/Qwen2.5-0.5B-Instruct&lt;/td&gt;
&lt;td&gt;&lt;code&gt;7ae557604adf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;494,032,768&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen/Qwen2.5-1.5B-Instruct&lt;/td&gt;
&lt;td&gt;&lt;code&gt;989aa7980e4c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1,543,714,304&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;microsoft/Phi-3-mini-4k-instruct&lt;/td&gt;
&lt;td&gt;&lt;code&gt;f39ac1d28e92&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3,821,079,552&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;allenai/OLMo-1B-hf&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aee7752d9c08&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1,176,764,416&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Same" is not a shrug in that column. Both paths answered 200, both returned the same config values (I compared vocabulary size, layer count, hidden size, tie flag, dtype and architecture string), both returned the same byte count — 861, 846, 908, 659, 660, 967 and 632 — and both carried the &lt;em&gt;same&lt;/em&gt; &lt;code&gt;ETag&lt;/code&gt; header. Seven out of seven.&lt;/p&gt;

&lt;p&gt;That last part is what I did not expect, and it is exactly the trap. An equality that holds today, with nothing behind it that keeps it holding. The &lt;code&gt;ETag&lt;/code&gt; earns you one true sentence: these two paths currently resolve to the same object. It does not promise they will at the moment your script runs tomorrow, because neither path is asking for a particular commit. Only the sha does that, for the plain reason that the sha &lt;em&gt;is&lt;/em&gt; the request.&lt;/p&gt;

&lt;h2&gt;
  
  
  What pinning costs, and a correction
&lt;/h2&gt;

&lt;p&gt;In the reply I sent that reader, I wrote that pinning the revision costs one extra request. Measured properly, that sentence is missing its condition, so let me put the condition back.&lt;/p&gt;

&lt;p&gt;If your script already calls &lt;code&gt;api/models/{repo}&lt;/code&gt; — mine did, because it needed &lt;code&gt;safetensors.total&lt;/code&gt; — then the &lt;code&gt;sha&lt;/code&gt; arrives in that very response, sitting next to the total. Pinning then costs &lt;strong&gt;zero&lt;/strong&gt; extra requests: the same two calls, with one of them naming a sha instead of &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The extra request only appears in the other case: a script whose only read is &lt;code&gt;raw/main/config.json&lt;/code&gt;, because all it wants is the config. That one goes from one call to two.&lt;/p&gt;

&lt;p&gt;So the honest version is: zero if you were already asking the API for anything, one if you were not. I wrote down the second half as if it were the whole answer. Not a large error, but it is the kind that survives because it sounds like a cost, and costs are the thing you are allowed to round.&lt;/p&gt;

&lt;h2&gt;
  
  
  The order that removes the ambiguity
&lt;/h2&gt;

&lt;p&gt;Three steps, and no new dependency:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;GET api/models/{repo}&lt;/code&gt; and keep &lt;code&gt;sha&lt;/code&gt; — plus &lt;code&gt;safetensors.total&lt;/code&gt; if your comparison needs it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET raw/{sha}/config.json&lt;/code&gt;, and recompute what the checkpoint stores from the config you just pinned.&lt;/li&gt;
&lt;li&gt;Compare the two numbers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After that, if the comparison still fails, the difference is real: you can no longer be comparing two dates by accident.&lt;/p&gt;

&lt;p&gt;The change is one string in a URL. What it buys is that every number in your table belongs to the same commit, and that someone who re-runs your script next month gets the same artifact you described — or an error, which is also an answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did not measure
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Whether any of these seven repos actually moved between two of my reads.&lt;/strong&gt; I cannot replay the past, and the identical &lt;code&gt;ETag&lt;/code&gt;s only say the paths agree now. The failure my reader described is plausible and cheap to prevent; I did not observe it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gated repos.&lt;/strong&gt; All seven I tried were ungated and answered 200. A gated repo may treat &lt;code&gt;raw/{sha}&lt;/code&gt; differently from &lt;code&gt;raw/main&lt;/code&gt;, and I do not know which way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The web UI's metadata.&lt;/strong&gt; I read the API and the raw file, not the page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whether &lt;code&gt;main&lt;/code&gt; had moved at all since the post my reader was reading.&lt;/strong&gt; That post is a day old; its own reads are not reproducible now, which is the whole point.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Seven repos is not a survey. It is seven repos, named above, so you can run the same seven and check me.&lt;/p&gt;

</description>
      <category>python</category>
      <category>api</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Eight model repos, added up: the file list and the model disagree</title>
      <dc:creator>pm25coder</dc:creator>
      <pubDate>Fri, 06 Nov 2026 10:53:49 +0000</pubDate>
      <link>https://dev.to/pm25coder/eight-model-repos-added-up-the-file-list-and-the-model-disagree-387a</link>
      <guid>https://dev.to/pm25coder/eight-model-repos-added-up-the-file-list-and-the-model-disagree-387a</guid>
      <description>&lt;h1&gt;
  
  
  Eight model repos, added up: the file list and the model disagree
&lt;/h1&gt;

&lt;p&gt;I wanted one number: how big is this model. Not a parameter count, not a formula — the bytes that land on disk when you pull it down.&lt;/p&gt;

&lt;p&gt;So I did the dumbest possible thing. I asked eight model repos on Hugging Face for their file list, added up every file that looks like weights, and compared that sum with the single file I would actually load. One request each, no token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://huggingface.co/api/models/openai-community/gpt2?blobs=true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;siblings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rfilename&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lfs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seven of the eight repos came back with a number between 2.0× and 9.7× the bytes I would download.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;repo&lt;/th&gt;
&lt;th&gt;weight files&lt;/th&gt;
&lt;th&gt;of those, in a subdirectory&lt;/th&gt;
&lt;th&gt;sum of every weight file&lt;/th&gt;
&lt;th&gt;one loadable file&lt;/th&gt;
&lt;th&gt;ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;sentence-transformers/all-MiniLM-L6-v2&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;884,718,327&lt;/td&gt;
&lt;td&gt;90,868,376&lt;/td&gt;
&lt;td&gt;9.74×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;openai-community/gpt2&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4,054,448,846&lt;/td&gt;
&lt;td&gt;548,105,171&lt;/td&gt;
&lt;td&gt;7.40×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HuggingFaceTB/SmolLM2-135M-Instruct&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1,966,318,064&lt;/td&gt;
&lt;td&gt;269,060,552&lt;/td&gt;
&lt;td&gt;7.31×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;google-bert/bert-base-uncased&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2,918,975,670&lt;/td&gt;
&lt;td&gt;440,449,768&lt;/td&gt;
&lt;td&gt;6.63×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;distilbert/distilbert-base-uncased&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1,167,291,991&lt;/td&gt;
&lt;td&gt;267,954,768&lt;/td&gt;
&lt;td&gt;4.36×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;facebook/opt-125m&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;751,734,738&lt;/td&gt;
&lt;td&gt;250,540,281&lt;/td&gt;
&lt;td&gt;3.00×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EleutherAI/pythia-70m&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;332,078,951&lt;/td&gt;
&lt;td&gt;166,029,852&lt;/td&gt;
&lt;td&gt;2.00×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen/Qwen2.5-0.5B-Instruct&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;988,097,824&lt;/td&gt;
&lt;td&gt;988,097,824&lt;/td&gt;
&lt;td&gt;1.00×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row is the control, and it is the one that tells you what the other seven rows are saying. &lt;code&gt;Qwen2.5-0.5B-Instruct&lt;/code&gt; ships exactly one weight file, so its file list and its model are the same object and the ratio is 1. The others are not wrong about their own contents; they just contain more than one copy of the same weights, in more than one container.&lt;/p&gt;

&lt;h2&gt;
  
  
  One model, several containers
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;gpt2&lt;/code&gt; is 137,022,720 parameters, which the Hub reports as &lt;code&gt;safetensors.total&lt;/code&gt;. Its repo holds seven weight files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;onnx/decoder_model_merged.onnx        655,189,339
onnx/decoder_with_past_model.onnx     653,672,649
onnx/decoder_model.onnx               653,665,842
pytorch_model.bin                     548,118,077
model.safetensors                     548,105,171
tf_model.h5                           497,933,648
flax_model.msgpack                    497,764,120
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four of them are the same 137M weights written by four different libraries — PyTorch, safetensors, TensorFlow, Flax. The three &lt;code&gt;.onnx&lt;/code&gt; files are graph exports for different decoding paths. Load one, and you download 523 MiB. Sum the list, and you get 3.78 GiB, which is 7.4× the file you would load, and none of that extra is more model.&lt;/p&gt;

&lt;p&gt;Nobody would do that on purpose. But "sum the files that look like weights" is exactly what a crawler, a cost estimate, or a "repo size" badge does, and the result is internally consistent — it adds up, it is just not the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  One blob, several names
&lt;/h2&gt;

&lt;p&gt;Two of the eight repos list the same bytes under more than one name. Not similar bytes — the same &lt;code&gt;lfs.sha256&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sentence-transformers/all-MiniLM-L6-v2
  23,026,053 bytes  -&amp;gt;  onnx/model_qint8_arm64.onnx
                        onnx/model_qint8_avx512.onnx
                        onnx/model_qint8_avx512_vnni.onnx

HuggingFaceTB/SmolLM2-135M-Instruct
  137,147,981 bytes -&amp;gt;  onnx/model_quantized.onnx
                        onnx/model_uint8.onnx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three names, one object. Summing the file list counts that 23 MB three times; deduplicating by hash brings &lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt; from 884,718,327 down to 838,666,221, and the difference is 46,052,106 — exactly two extra copies. Same arithmetic on &lt;code&gt;SmolLM2-135M-Instruct&lt;/code&gt;: 1,966,318,064 minus 1,829,170,083 is 137,147,981, exactly the one duplicated pair.&lt;/p&gt;

&lt;p&gt;So a file list is not a set. If you are counting storage, it is a multiset and you have to collapse it by hash first. If you are counting what a user downloads, it is not even that, because they will only ever take one of those formats.&lt;/p&gt;

&lt;h2&gt;
  
  
  No safetensors at all: null is not zero
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;facebook/opt-125m&lt;/code&gt; contains no &lt;code&gt;.safetensors&lt;/code&gt; file. Three weight containers, all of them other formats. Ask the API for its parameter count and you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;safetensors: null
safetensors.total: None
safetensors.parameters: {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not zero — absent. Which means a pipeline that does &lt;code&gt;total = d["safetensors"]["total"] or 0&lt;/code&gt; will record a 125-million-parameter model as having zero parameters, and nothing downstream will complain. That is not a hypothetical formatting problem; it is the same shape as reading a missing field as an empty result, and the number it produces is a perfectly ordinary-looking 0.&lt;/p&gt;

&lt;p&gt;If you need a parameter count for a repo that ships only &lt;code&gt;.bin&lt;/code&gt;, &lt;code&gt;.h5&lt;/code&gt; or &lt;code&gt;.msgpack&lt;/code&gt;, you have to open the files. No field will answer for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why my first number was wrong
&lt;/h2&gt;

&lt;p&gt;The duplicate-blob section above is the second version of this article. The first version said something else: that none of the eight repos stored the same bytes twice.&lt;/p&gt;

&lt;p&gt;I wrote the check to read &lt;code&gt;siblings[i]["lfs"]["oid"]&lt;/code&gt;. There is no &lt;code&gt;oid&lt;/code&gt; key there. The real field is &lt;code&gt;lfs.sha256&lt;/code&gt;. My extractor returned &lt;code&gt;None&lt;/code&gt; for every file, the deduplication map came back empty, and the script printed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;oids_in_weight_files=0  oids_under_two_or_more_names=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for all eight repos. Read on its own, that line is a clean bill of health. What caught it was the line directly above it, which said every one of those files was LFS-backed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;onnx/decoder_model_merged.onnx    655189339  lfs=True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You cannot have seven LFS files and zero content hashes. The two readings contradicted each other, and the contradiction was the bug report.&lt;/p&gt;

&lt;p&gt;The general shape is worth more than the field name: a lookup into a structure you have not actually printed will return "nothing" for every input, and "nothing" is the same answer you would get if the thing genuinely was not there. I got lucky because a second column disagreed. Usually nothing disagrees, and you publish the zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do instead of adding up files
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For "what will this cost me to download"&lt;/strong&gt; — pick the one file you would load, and quote its size. &lt;code&gt;model.safetensors&lt;/code&gt; if it exists, otherwise the largest single &lt;code&gt;.bin&lt;/code&gt;. The file list is not the answer to this question.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For "how many parameters"&lt;/strong&gt; — &lt;code&gt;safetensors.total&lt;/code&gt; is the Hub's own count over the safetensors files, and where it exists it agreed with the per-format arithmetic I checked it against. Where it does not exist it is &lt;code&gt;null&lt;/code&gt;, and &lt;code&gt;null&lt;/code&gt; is a different word than &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For "how much space does this repo occupy"&lt;/strong&gt; — collapse by &lt;code&gt;lfs.sha256&lt;/code&gt; first. Two names, one blob, and the blob is charged once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you filter the file list by anything&lt;/strong&gt;, filter by both name and location. &lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt; keeps 11 of its 14 weight files in subdirectories; a filter that only looks at root-level filenames sees 3 of 14 and reports a repo that is three times smaller than itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this needs a token, a script library, or a download. It is one request per repo and some arithmetic, and the arithmetic is the part that catches you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did not measure
&lt;/h2&gt;

&lt;p&gt;Whether a second copy of the weights in a subdirectory inflates &lt;code&gt;safetensors.total&lt;/code&gt; the way it inflates a naive file sum. None of the eight repos I opened keeps two &lt;code&gt;.safetensors&lt;/code&gt; files, so I had nothing to test it on, and I am not going to guess at the Hub's behaviour from its output on a different shape. If you know a repo that ships &lt;code&gt;model.safetensors&lt;/code&gt; and a copy of it under a subdirectory, that is the experiment, and it is one request.&lt;/p&gt;

&lt;p&gt;Eight repos is also not a survey. It is eight repos, chosen because I already had them open, and the ratios above are what those eight contain.&lt;/p&gt;

</description>
      <category>python</category>
      <category>llm</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>The model card said 0.5B. The checkpoint stores 494M. Both are right.</title>
      <dc:creator>pm25coder</dc:creator>
      <pubDate>Fri, 06 Nov 2026 07:46:12 +0000</pubDate>
      <link>https://dev.to/pm25coder/the-model-card-said-05b-the-checkpoint-stores-494m-both-are-right-39kh</link>
      <guid>https://dev.to/pm25coder/the-model-card-said-05b-the-checkpoint-stores-494m-both-are-right-39kh</guid>
      <description>&lt;p&gt;Qwen2.5-0.5B's config declares its vocabulary-by-hidden matrix twice: once as the embedding table, once as the output head. Its checkpoint stores that matrix once. Both statements are true, and the distance between them is 136,134,656 parameters, which is 27.6% of everything in the repo.&lt;/p&gt;

&lt;p&gt;I went and measured that this morning, because a reader asked whether &lt;code&gt;safetensors.total&lt;/code&gt; — the parameter count Hugging Face reports for a repo — can be trusted. The post he was replying to divides bytes on disk by that number. If the denominator is decoration, so is the quotient.&lt;/p&gt;

&lt;p&gt;So: six checkpoints, two of them with tied embeddings, two of them mixture-of-experts, each one's stored tensor count recomputed from its own &lt;code&gt;config.json&lt;/code&gt; and compared against what the API reports.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;checkpoint&lt;/th&gt;
&lt;th&gt;&lt;code&gt;safetensors.total&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;recomputed from config&lt;/th&gt;
&lt;th&gt;residual&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Qwen/Qwen2.5-0.5B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;494,032,768&lt;/td&gt;
&lt;td&gt;494,032,768&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Qwen/Qwen2.5-7B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7,615,616,512&lt;/td&gt;
&lt;td&gt;7,615,616,512&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HuggingFaceTB/SmolLM2-135M&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;134,515,008&lt;/td&gt;
&lt;td&gt;134,515,008&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;microsoft/Phi-3-mini-4k-instruct&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3,821,079,552&lt;/td&gt;
&lt;td&gt;3,821,079,552&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Qwen/Qwen1.5-MoE-A2.7B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;14,315,784,192&lt;/td&gt;
&lt;td&gt;14,315,784,192&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mistralai/Mixtral-8x7B-v0.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;46,702,792,704&lt;/td&gt;
&lt;td&gt;46,702,792,704&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Zero on all six. The field is not the part that lies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three counts fit in one checkpoint
&lt;/h2&gt;

&lt;p&gt;The part that lies is the comparison, because "how many parameters" has three answers and a checkpoint makes it very easy to hold one answer in each hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stored&lt;/strong&gt; is what the tensors in the repo add up to. That is what &lt;code&gt;safetensors.total&lt;/code&gt; reports, it is what you download, and it is the only count that describes the bytes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural&lt;/strong&gt; is what the config implies. A model with tied embeddings computes its logits by reusing the embedding matrix instead of storing a second copy, so the architecture names that matrix twice while the file stores it once. Nothing is missing and nothing is wrong; the two numbers answer different questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Active per token&lt;/strong&gt; is what one forward pass actually reads. In a mixture-of-experts layer every expert is stored and few are used, so this count is much smaller than the checkpoint.&lt;/p&gt;

&lt;p&gt;Here is all three for the same six repos:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;checkpoint&lt;/th&gt;
&lt;th&gt;stored&lt;/th&gt;
&lt;th&gt;architectural&lt;/th&gt;
&lt;th&gt;gap&lt;/th&gt;
&lt;th&gt;one token reads&lt;/th&gt;
&lt;th&gt;stored / active&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Qwen/Qwen2.5-0.5B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;494,032,768&lt;/td&gt;
&lt;td&gt;630,167,424&lt;/td&gt;
&lt;td&gt;+27.6%&lt;/td&gt;
&lt;td&gt;494,032,768&lt;/td&gt;
&lt;td&gt;1.00x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HuggingFaceTB/SmolLM2-135M&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;134,515,008&lt;/td&gt;
&lt;td&gt;162,826,560&lt;/td&gt;
&lt;td&gt;+21.0%&lt;/td&gt;
&lt;td&gt;134,515,008&lt;/td&gt;
&lt;td&gt;1.00x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Qwen/Qwen2.5-7B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7,615,616,512&lt;/td&gt;
&lt;td&gt;7,615,616,512&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;td&gt;7,615,616,512&lt;/td&gt;
&lt;td&gt;1.00x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;microsoft/Phi-3-mini-4k-instruct&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3,821,079,552&lt;/td&gt;
&lt;td&gt;3,821,079,552&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;td&gt;3,821,079,552&lt;/td&gt;
&lt;td&gt;1.00x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Qwen/Qwen1.5-MoE-A2.7B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;14,315,784,192&lt;/td&gt;
&lt;td&gt;14,315,784,192&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;td&gt;2,689,173,504&lt;/td&gt;
&lt;td&gt;5.32x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mistralai/Mixtral-8x7B-v0.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;46,702,792,704&lt;/td&gt;
&lt;td&gt;46,702,792,704&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;td&gt;12,879,925,248&lt;/td&gt;
&lt;td&gt;3.63x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things fall out of that table. The tied models are the only ones where the stored count and the architectural count disagree, and they disagree by 21% and 27.6%. The MoE models are the only ones where the stored count and the active count disagree, and they disagree by 5.3x and 3.6x.&lt;/p&gt;

&lt;p&gt;And then there is the name. &lt;code&gt;Mixtral-8x7B&lt;/code&gt; sounds like 56 billion parameters. The repo stores 46,702,792,704 and a token reads 12,879,925,248, so the name is a description of the architecture, not of the artifact. It is not wrong to say it — it is wrong to divide by it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The check itself
&lt;/h2&gt;

&lt;p&gt;Fifteen lines, one file each for the config and the reported total. Nothing here needs the weights.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl/8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stored_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;heads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hidden_size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num_attention_heads&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;kv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num_key_value_heads&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;heads&lt;/span&gt;
    &lt;span class="n"&gt;hd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;heads&lt;/span&gt;
    &lt;span class="n"&gt;vocab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vocab_size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;attn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;hd&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attention_bias&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qwen2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;attn&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;hd&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num_experts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num_local_experts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;moe_intermediate_size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;intermediate_size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;mlp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;moe_intermediate_size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shared_expert_intermediate_size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;mlp&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shared_expert_intermediate_size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mlp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;intermediate_size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num_hidden_layers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attn&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;mlp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;vocab&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tie_word_embeddings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Qwen/Qwen2.5-0.5B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://huggingface.co/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/raw/main/config.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;reported&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://huggingface.co/api/models/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;safetensors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reported&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;stored_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;reported&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;stored_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The residual is the useful part, and it is useful in the direction you might not expect: a non-zero residual means the model does something your formula has not accounted for yet, so the check tells you about your own code rather than about the repo. Which is how I learned what the next section says.&lt;/p&gt;

&lt;h2&gt;
  
  
  The residuals before they were zero
&lt;/h2&gt;

&lt;p&gt;My first pass used one formula for six architectures and got three non-zero residuals. All three were my arithmetic, none were the field.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;27,648 on Qwen2.5-0.5B.&lt;/strong&gt; That is 24 layers times (896 + 128 + 128), the q, k and v bias vectors. Qwen2 has &lt;code&gt;attention_bias&lt;/code&gt; on by default and its config does not mention it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1,048,576 on Mixtral.&lt;/strong&gt; That is 32 layers times 4096 times 8, the router gate: one 4096-wide row per expert for choosing experts. The same pass also had an entire dense MLP per layer that Mixtral does not have, because there &lt;code&gt;intermediate_size&lt;/code&gt; is the width of an expert, not of a shared MLP. That mistake was worth 39.5 billion and could not hide; the router gate was 0.002% and hid perfectly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;196,608 on Qwen1.5-MoE.&lt;/strong&gt; The q, k and v biases again, plus &lt;code&gt;shared_expert_gate&lt;/code&gt;, a per-layer projection the config never names.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the ratio to keep in mind: the number the file reports came out exact on six architectures, and the number I computed needed three corrections to get there. When those two disagree, the derived one is the suspect.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did not measure
&lt;/h2&gt;

&lt;p&gt;Two of the six ship their weights in a single file, so there is no &lt;code&gt;model.safetensors.index.json&lt;/code&gt; to read and I could not check the tensor names directly (that URL answers with 15 bytes, not JSON). For those two, "the matrix is stored once" is arithmetic rather than an inventory: 494,032,768 matches the tied count to the byte and is exactly one vocab-by-hidden matrix below the untied count. For the four sharded repos I did read the weight map, and &lt;code&gt;lm_head.weight&lt;/code&gt; is a stored tensor in every untied model and in neither tied one.&lt;/p&gt;

&lt;p&gt;I have not measured GGUF or other quantized repos. &lt;code&gt;safetensors.total&lt;/code&gt; describes safetensors files; a repo that ships only GGUF does not have that field, and I do not know what the API returns for one.&lt;/p&gt;

&lt;p&gt;I have also not measured the case where a repo keeps a second copy of the weights in a subfolder. That is a different failure — the sum closes on the wrong number — and it makes any total wrong in the same direction, which is why the filter in the last post mattered more than I gave it credit for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule
&lt;/h2&gt;

&lt;p&gt;For bits per parameter, the pair that means anything is bytes on disk over parameters stored, because both numbers describe the same artifact. The architectural count answers a different question, and it is a fine question; it just is not this one. If you need the active count, name it out loud — "14.3B stored, 2.7B active" is a sentence, and "14.3B" alone is how a name like 8x7B survives as if it were a measurement.&lt;/p&gt;

&lt;p&gt;If you want to run this on a checkpoint of your own: the config, the model endpoint, and the function above are the whole apparatus. A residual of zero means the field and your model of the model agree. Anything else means you have just found the interesting part.&lt;/p&gt;

</description>
      <category>python</category>
      <category>llm</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>The model page said 8-bit. The checkpoint is 4.49 bits per parameter.</title>
      <dc:creator>pm25coder</dc:creator>
      <pubDate>Thu, 05 Nov 2026 01:14:58 +0000</pubDate>
      <link>https://dev.to/pm25coder/the-model-page-said-8-bit-the-checkpoint-is-449-bits-per-parameter-387g</link>
      <guid>https://dev.to/pm25coder/the-model-page-said-8-bit-the-checkpoint-is-449-bits-per-parameter-387g</guid>
      <description>&lt;p&gt;I was working out how much disk Kimi K3.1 Pro would need, and the model page gave me two numbers that cannot both be true. The card says &lt;code&gt;MXFP4 weights / MXFP8 activations&lt;/code&gt;. The badge beside the download button says &lt;code&gt;8-bit precision&lt;/code&gt;. So I added up the shards I would actually pull and divided by the parameter count the platform reports, and got 4.49 bits per parameter.&lt;/p&gt;

&lt;p&gt;That is not a rounding difference. 2.78 trillion parameters at 8 bits is 2,780 GB; at 4 bits it is 1,390 GB. The 96 shards in that repository come to 1,561 GB. So one of those three numbers is answering a question I did not ask, and if you are sizing a download or a disk you need to know which one it is.&lt;/p&gt;

&lt;p&gt;The check is fifteen lines and it runs against the public API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;

&lt;span class="n"&gt;REPO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;moonshotai/Kimi-K3.1-Pro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://huggingface.co/api/models/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;REPO&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;?blobs=true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;siblings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
         &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rfilename&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.safetensors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rfilename&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;

&lt;span class="n"&gt;weight_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;safetensors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; shards, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;weight_bytes&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1e9&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; GB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; weights, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;weight_bytes&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bits each&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details in it carry more weight than the arithmetic. &lt;code&gt;?blobs=true&lt;/code&gt; is what makes the endpoint return a size per file at all; without it you get file names and nothing to add up. And the &lt;code&gt;"/" not in ...&lt;/code&gt; filter keeps only the shards sitting at the top of the repository, because plenty of repositories keep other copies of the same weights in subfolders. I will come back to that filter, because it is the line that saved me from a wrong answer I had already written down.&lt;/p&gt;

&lt;p&gt;For Kimi K3.1 Pro it prints 96 shards, 1,561.3 GB, 2,780,110,363,392 weights, 4.4928 bits each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the 1,561 GB goes
&lt;/h2&gt;

&lt;p&gt;The number stops being mysterious once you subtract the three things a four-bit checkpoint always contains.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;what it is&lt;/th&gt;
&lt;th&gt;bytes&lt;/th&gt;
&lt;th&gt;GB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4-bit weights, at half a byte each&lt;/td&gt;
&lt;td&gt;1,361,370,415,104&lt;/td&gt;
&lt;td&gt;1,361.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tensors kept at 16 and 32 bits&lt;/td&gt;
&lt;td&gt;114,761,311,232&lt;/td&gt;
&lt;td&gt;114.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;block scales, one byte per 32 weights&lt;/td&gt;
&lt;td&gt;85,085,650,944&lt;/td&gt;
&lt;td&gt;85.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;the three terms together&lt;/td&gt;
&lt;td&gt;1,561,217,377,280&lt;/td&gt;
&lt;td&gt;1,561.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;the files on disk&lt;/td&gt;
&lt;td&gt;1,561,293,160,568&lt;/td&gt;
&lt;td&gt;1,561.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;left over&lt;/td&gt;
&lt;td&gt;138,589,683&lt;/td&gt;
&lt;td&gt;0.139&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three terms take you to within 139 MB of the download, which is 0.009% of it. That is what convinced me the 4.49 is a reading of the checkpoint and not a misunderstanding of it: the dtype counts the platform publishes, half a byte per four-bit weight, plus the tensors it left alone, plus one scale byte per 32, lands on the file size.&lt;/p&gt;

&lt;p&gt;The interesting part is the middle row. Some of that model is not four bit at all — 114.8 GB of it is kept at 16 and 32 bits, either because quantizing those layers costs quality nobody wanted to spend or because they are too small to be worth packing. Whatever the reason, a "4-bit model" is a description of the bulk, not of every byte.&lt;/p&gt;

&lt;p&gt;And the card's parameter count holds up. The platform's counts add to 2,780,110,363,392, which is the card's 2.8T rounded. So the vendor and the platform agree on how many weights there are. They disagree only on what each one costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number that talked me out of my own check
&lt;/h2&gt;

&lt;p&gt;Before I pointed this at Kimi, I had it pointed at a model whose weights I already knew were four bit: &lt;code&gt;openai/gpt-oss-120b&lt;/code&gt;. It printed 195.8 GB and 13.405 bits per parameter. That is nonsense on its face — 116.8 billion weights do not cost 13 bits each in a four-bit checkpoint.&lt;/p&gt;

&lt;p&gt;The repository holds the same weights three times over: the mxfp4 shards at the top of the tree, &lt;code&gt;original/&lt;/code&gt; at 65.2 GB, and &lt;code&gt;metal/&lt;/code&gt; at 65.2 GB. Every one of them went into my sum. Restricted to the 15 top-level shards it is 65.2 GB, and 4.4680 bits per parameter, and the same three-term breakdown predicts 65.2 GB to the tenth.&lt;/p&gt;

&lt;p&gt;The lesson is not "be careful with sums". A checkpoint repository is not a file, it is a directory that may contain several encodings of the same weights, so "how big is this model" has one answer per encoding, and my first instrument did not know which question it was answering. It reported a number three times too large with complete confidence, and it would have gone into this post if I had not had a second model to compare against.&lt;/p&gt;

&lt;h2&gt;
  
  
  The badge
&lt;/h2&gt;

&lt;p&gt;Both of those repositories are tagged &lt;code&gt;8-bit&lt;/code&gt; by the platform. Neither is tagged &lt;code&gt;4-bit&lt;/code&gt;. gpt-oss-120b also carries &lt;code&gt;mxfp4&lt;/code&gt;, so the same tag list will tell you both things about the same repository at the same time.&lt;/p&gt;

&lt;p&gt;I do not know how the badge is computed, and I am not going to guess in a sentence that reads like a finding. What I can report is what it says on two checkpoints whose weights are unambiguously four bit, and on both of them it says eight. If the badge is reading the storage dtype, that would explain it: a packed four-bit weight sits inside an unsigned 8-bit container, so the dtype of the tensor is &lt;code&gt;U8&lt;/code&gt; even though two weights share a byte. That is a guess about somebody else's code, it is testable only from the inside, and I have marked it as a guess.&lt;/p&gt;

&lt;p&gt;What I can say without guessing is that the badge is a summary, not a measurement of your download, and the arithmetic above is five lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did not do
&lt;/h2&gt;

&lt;p&gt;I did not download 1.5 TB. I did not load the model. I have no throughput, no VRAM figure, no benchmark for it — every number here came from the two API responses this check reads, and from nothing else.&lt;/p&gt;

&lt;p&gt;There is one soft spot in the accounting and it belongs in the post rather than in my notes. Block scales are not exposed per tensor in that API, so I assumed the common one byte per 32 weights. It fits to 0.009%, and I cannot tell from the outside whether that is a correct assumption or a lucky one. If some architecture uses a different block size, the middle of that table would drift and the total would still look plausible, which is the shape of error I like least.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do with it
&lt;/h2&gt;

&lt;p&gt;If you are sizing a download, the number you want is the bytes under the path you will actually pull, divided by the weight count the card claims. Compare that to the precision the card names. When they disagree by a factor of two, you are reading two different claims, and the one that describes bytes on a disk is the one your disk will enforce.&lt;/p&gt;

&lt;p&gt;The card is the vendor telling you how the model was trained. The badge is the platform telling you what it found in the files. Neither of them is lying here — an 8-bit activation scheme and a 4-bit weight scheme can both be true of the same model, and somewhere between those two facts the word "precision" stopped meaning one thing. Your disk does not have that problem. It will hold 1,561 GB or it will not, and no label changes the answer.&lt;/p&gt;

</description>
      <category>python</category>
      <category>llm</category>
      <category>ai</category>
      <category>devops</category>
    </item>
    <item>
      <title>Postman approved its own hook. Newman approves its own collection.</title>
      <dc:creator>pm25coder</dc:creator>
      <pubDate>Wed, 04 Nov 2026 14:15:16 +0000</pubDate>
      <link>https://dev.to/pm25coder/postman-approved-its-own-hook-newman-approves-its-own-collection-hmk</link>
      <guid>https://dev.to/pm25coder/postman-approved-its-own-hook-newman-approves-its-own-collection-hmk</guid>
      <description>&lt;p&gt;On October 25 a developer opened Postman on a Mac and found their coding agents had been changed. A Postman plugin had been installed into Codex and Claude Code; a session-start hook had been registered; and inside their own &lt;code&gt;~/.codex/config.toml&lt;/code&gt; a trust record had been written for that hook, a &lt;code&gt;trusted_hash&lt;/code&gt; under a &lt;code&gt;hooks.state&lt;/code&gt; key naming the hook file the installer had just put there.&lt;/p&gt;

&lt;p&gt;That report is &lt;code&gt;postmanlabs/postman-app-support#14370&lt;/code&gt;. When I read it, it was still open, with 315 thumbs-up and 51 comments, on desktop build 12.33.4.&lt;/p&gt;

&lt;p&gt;I cannot measure that. I have no Postman desktop install here, and I am not going to build a claim about someone's app out of a screenshot. What held my attention was one sentence the reporter wrote about his own evidence: the source shows the approval attempt, but the trust record alone does not prove who created it, so he does not present it as proof of a particular interaction.&lt;/p&gt;

&lt;p&gt;That sentence is the whole post in miniature. &lt;strong&gt;A record that says "trusted" is not a measurement of "I chose this."&lt;/strong&gt; The record is real. The claim it appears to support is one step larger than the record.&lt;/p&gt;

&lt;p&gt;So I went looking for the same missing measurement in the tool that runs API tests, where I can run everything myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it prints when it runs one request out of three
&lt;/h2&gt;

&lt;p&gt;A collection with three requests. The first sends and asserts. The other two assert &lt;code&gt;200&lt;/code&gt; against an endpoint that returns &lt;code&gt;500&lt;/code&gt;, so they would fail loudly if they ran. The first request's prerequest script calls &lt;code&gt;postman.setNextRequest(null)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;newman 6.2.3, exit code &lt;strong&gt;0&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;│              iterations │   1 executed │   0 failed │
│                requests │   1 executed │   0 failed │
│              assertions │   2 executed │   0 failed │
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every number there is true. One request was executed, two assertions ran, nothing failed. The collection has three requests.&lt;/p&gt;

&lt;p&gt;Nothing in that table has a denominator. It says &lt;code&gt;1&lt;/code&gt; next to the word &lt;code&gt;requests&lt;/code&gt;, and you have to already know the collection had three to notice that this run did two-thirds less work than the run that passes in the good week. The table cannot tell the two apart, and neither can the exit code, and neither can your CI badge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The other ways it stays green
&lt;/h2&gt;

&lt;p&gt;Everything below is newman 6.2.3 on one Windows machine, against a local Python server that returns &lt;code&gt;200&lt;/code&gt; on one path and &lt;code&gt;500&lt;/code&gt; on another.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;collection&lt;/th&gt;
&lt;th&gt;newman exit&lt;/th&gt;
&lt;th&gt;requests&lt;/th&gt;
&lt;th&gt;assertions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3 requests, two skipped by &lt;code&gt;setNextRequest(null)&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 executed, 0 failed&lt;/td&gt;
&lt;td&gt;2 executed, 0 failed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 request, no test script at all&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 executed, 0 failed&lt;/td&gt;
&lt;td&gt;0 executed, 0 failed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;empty collection, no items&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0 executed, 0 failed&lt;/td&gt;
&lt;td&gt;0 executed, 0 failed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;control: &lt;code&gt;500&lt;/code&gt; response and a bare assertion&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 executed, 0 failed&lt;/td&gt;
&lt;td&gt;1 executed, 1 failed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The control matters as much as the others. Point a failing assertion at a real &lt;code&gt;500&lt;/code&gt; and newman does exit 1 and does print the failure. The instrument is not broken. It answers the question it was asked.&lt;/p&gt;

&lt;p&gt;The empty collection is the one that should worry you. Exit code 0, total run duration 3 ms, 0 B received, and a summary table full of zeros that looks exactly like a clean sweep. An empty collection is what you get when a config file is missing, when a path resolves to nothing, when the loop that builds the items takes a branch you did not expect. The runner reports that as success, and it is not lying: zero failures were found among zero assertions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three green checks that are not green over your suite
&lt;/h2&gt;

&lt;p&gt;I expected more of them to be red than were. Two arms I built to be quiet stayed red, which is worth saying before the other way round:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A &lt;code&gt;pm.test&lt;/code&gt; wrapped in &lt;code&gt;try/catch&lt;/code&gt; still fails.&lt;/strong&gt; I assumed swallowing the exception would swallow the assertion. It does not: &lt;code&gt;Assertions 1 executed, 1 failed&lt;/code&gt;, exit 1. Whatever newman does internally, it records the assertion before the throw leaves your script, so a &lt;code&gt;try/catch&lt;/code&gt; around &lt;code&gt;pm.test&lt;/code&gt; is not a way to lose a failure. I was wrong, and the arm cost me ten minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An assertion reading &lt;code&gt;pm.iterationData.get('id')&lt;/code&gt; with no data file fails.&lt;/strong&gt; Exit 1, &lt;code&gt;expected undefined to be a string&lt;/code&gt;. Also wrong in my favour.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the holes are not "assertions can be lost". They are all about the size of the set:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Requests that were skipped&lt;/strong&gt; by a control-flow call are not distinguishable from requests that passed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A collection with no assertions&lt;/strong&gt; passes, correctly, having asserted nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A collection with no requests&lt;/strong&gt; passes, in 3 milliseconds.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  One line that turns two of those red
&lt;/h2&gt;

&lt;p&gt;newman's JSON reporter writes everything it did to a file, so the comparison I want is available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npx newman run collection.json &lt;span class="nt"&gt;--reporters&lt;/span&gt; json &lt;span class="nt"&gt;--reporter-json-export&lt;/span&gt; report.json
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"const r=require('./report.json'),c=require('./collection.json');&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;const n=i=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;i.reduce((a,x)=&amp;gt;a+(x.item?n(x.item):1),0);&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;const ran=r.run.executions.length,declared=n(c.item);&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;console.log(ran+' of '+declared+' requests executed');&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="go"&gt;if(ran!==declared)process.exit(1)"
1 of 3 requests executed
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="go"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the passing-looking run above, that prints &lt;code&gt;1 of 3 requests executed&lt;/code&gt; and exits 1. Your CI goes red on the collection that skipped two requests, while newman itself stays green.&lt;/p&gt;

&lt;p&gt;Then I ran the same line against the other three collections, and it has a hole of exactly the kind this post is about:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;collection&lt;/th&gt;
&lt;th&gt;my check, version 1&lt;/th&gt;
&lt;th&gt;verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3 requests, two skipped&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1 of 3 requests executed&lt;/code&gt;, exit &lt;strong&gt;1&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;caught&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 request, no assertions&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1 of 1 requests executed&lt;/code&gt;, exit &lt;strong&gt;0&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;missed&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;empty collection&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;0 of 0 requests executed&lt;/code&gt;, exit &lt;strong&gt;0&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;missed&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;control (real failure)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1 of 1 requests executed&lt;/code&gt;, exit &lt;strong&gt;0&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;not its question&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row is fine and expected: that line compares counts, it does not look at failures, and newman already answers the failure question with its exit code. Two instruments, two questions.&lt;/p&gt;

&lt;p&gt;The third row is not fine. &lt;code&gt;0 of 0&lt;/code&gt; is the same shape as "the log file was missing, so zero calls were recorded, so the gate passed" — a zero that cannot distinguish &lt;em&gt;nothing happened&lt;/em&gt; from &lt;em&gt;nothing was recorded&lt;/em&gt;. My check compared a number to a number and was satisfied by both being zero. So the version I would actually put in a pipeline adds one clause:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;declared&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;ran&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;declared&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that clause the empty collection exits 1 and the skipped-request collection still exits 1. Two lines, and only the two arms that should pass do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three things worth adding to your pipeline
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run with &lt;code&gt;--reporters json&lt;/code&gt; and assert &lt;code&gt;executions.length&lt;/code&gt; against the declared item count.&lt;/strong&gt; The runner already writes both. Nobody compares them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refuse a collection with zero assertions.&lt;/strong&gt; Exit 0 over zero assertions is correct arithmetic and worthless assurance; if a collection is meant to be a regression gate, its assertion count belongs in the gate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Print the denominator next to the green.&lt;/strong&gt; The one number that made all of this visible was &lt;code&gt;3&lt;/code&gt;, taken from the collection file, not from the run. A summary that cannot print it is a summary of a different question.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;p&gt;newman is an open-source CLI runner; the desktop app in that GitHub issue is a different artifact, and none of the above is evidence about it. I have not installed Postman desktop, and I have not tested whether the plugin install, the hook registration or the trust-record write still happen on any build. Those are the reporter's measurements, not mine, and his own caveat about what the trust record proves is the reason I'm borrowing the shape and not the accusation.&lt;/p&gt;

&lt;p&gt;I also did not test other runners. Bruno, hurl, and the rest may skip requests in ways that surface differently; on this machine I measured newman 6.2.3 and nothing else, so treat the table as one runner on one day, not a survey.&lt;/p&gt;

&lt;p&gt;What I think does generalise is smaller than any of this. Both artifacts — a test summary and a trust record — are truthful about what they contain, and both get read as an answer to a question one size larger. In one case the question is "did my tests pass", and the answer came from whichever requests happened to run. In the other the question is "did I agree to this", and the answer came from a row that says the hook is trusted.&lt;/p&gt;

&lt;p&gt;Neither artifact was lying. Both were counted over a set that nobody named out loud. If you take one thing from this, take the habit: when something reports success, find the number it counted, and then find the number it should have counted. They are almost never the same one, and the second one is usually sitting in a file the first one already wrote.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>api</category>
      <category>javascript</category>
      <category>devops</category>
    </item>
    <item>
      <title>Your model card says 128k. The machine serving it picked 4k.</title>
      <dc:creator>pm25coder</dc:creator>
      <pubDate>Tue, 03 Nov 2026 02:07:23 +0000</pubDate>
      <link>https://dev.to/pm25coder/your-model-card-says-128k-the-machine-serving-it-picked-4k-1ib0</link>
      <guid>https://dev.to/pm25coder/your-model-card-says-128k-the-machine-serving-it-picked-4k-1ib0</guid>
      <description>&lt;p&gt;I got this one wrong first, and the reason is worth more than the correction.&lt;/p&gt;

&lt;p&gt;I assumed the effective context window was a property of the model file. Dump the GGUF header, read &lt;code&gt;context_length&lt;/code&gt;, compare it against what the model card claims, done. That check is real, but it answers a narrower question than I thought. &lt;strong&gt;The file can only push your context down. The number you actually get is chosen on the machine that loads the model, from the VRAM it can see, and 4096 is what falls out when nobody said otherwise.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So a box that is too small and a quantization that was built too small produce the same number through two different code paths, and only one of them is in the file you were told to inspect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The branch that picks the number
&lt;/h2&gt;

&lt;p&gt;From &lt;code&gt;ollama/ollama&lt;/code&gt;, &lt;code&gt;server/routes.go:2576-2586&lt;/code&gt; as of today:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Set default context based on VRAM tier&lt;/span&gt;
&lt;span class="c"&gt;// Use slightly lower thresholds (47/23 GiB vs. 48/24 GiB) to account for small differences in the exact value&lt;/span&gt;
&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;totalVRAM&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;47&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GibiByte&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;defaultNumCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;262144&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;totalVRAM&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;23&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GibiByte&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;defaultNumCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;32768&lt;/span&gt;
&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;defaultNumCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;4096&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"vram-based default context"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"total_vram"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HumanBytes2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;totalVRAM&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"default_num_ctx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;defaultNumCtx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;totalVRAM&lt;/code&gt; is summed over your devices as &lt;code&gt;gpu.TotalMemory - envconfig.GpuOverhead()&lt;/code&gt; (&lt;code&gt;:2571-2574&lt;/code&gt;), so the input to that switch is VRAM &lt;strong&gt;after&lt;/strong&gt; overhead, not the number printed on the box. The thresholds are 47 and 23 rather than 48 and 24 for that reason, which the comment above the switch says out loud. A 24 GiB card whose overhead eats a gigabyte lands in the 4k arm.&lt;/p&gt;

&lt;p&gt;At the other end of the same file, &lt;code&gt;envconfig/config.go:230&lt;/code&gt; declares the override and defaults it to zero:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;ContextLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Uint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OLLAMA_CONTEXT_LENGTH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and &lt;code&gt;:338&lt;/code&gt; documents what zero means: &lt;code&gt;Context length to use unless otherwise specified (default: 4k/32k/256k based on VRAM)&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the tier applies at all
&lt;/h2&gt;

&lt;p&gt;The VRAM number is not a fallback that runs every time. &lt;code&gt;server/routes.go:203-211&lt;/code&gt; returns false — meaning "do not use the automatic context" — if the request carries &lt;code&gt;num_ctx&lt;/code&gt;, or the stored model options carry &lt;code&gt;num_ctx&lt;/code&gt;, or &lt;code&gt;OLLAMA_CONTEXT_LENGTH&lt;/code&gt; is non-zero. Only when all three are silent does &lt;code&gt;:141-143&lt;/code&gt; copy the tier value into the request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultOptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumCtx&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;defaultNumCtx&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the sentence I would underline if I could mark up someone else's repository: 4k is not a default anyone chose. It is what remains after three separate places all declined to answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the file really does cap you
&lt;/h2&gt;

&lt;p&gt;One path &lt;em&gt;is&lt;/em&gt; about the file, and it only moves the number downward. &lt;code&gt;llm/server.go:109-114&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Verify the requested context size is &amp;lt;= the model training size&lt;/span&gt;
&lt;span class="n"&gt;trainCtx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KV&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContextLength&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumCtx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trainCtx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;trainCtx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"requested context size too large for model"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"num_ctx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"n_ctx_train"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trainCtx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trainCtx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;f.KV().ContextLength()&lt;/code&gt; comes out of the GGUF metadata, and llama.cpp is where that key is defined and read: &lt;code&gt;src/llama-arch.cpp:211&lt;/code&gt; maps it to &lt;code&gt;"%s.context_length"&lt;/code&gt; and &lt;code&gt;src/llama-model.cpp:1405&lt;/code&gt; assigns it to &lt;code&gt;hparams.n_ctx_train&lt;/code&gt;. So &lt;code&gt;gguf-dump&lt;/code&gt; answers exactly one question — &lt;em&gt;did this quantization get built with a small training context&lt;/em&gt; — and it answers it well. It cannot tell you your card is too small, because the card is not in the file.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that has no error message
&lt;/h2&gt;

&lt;p&gt;Below both of those is the code that quietly drops your early turns. &lt;code&gt;server/routes.go:3560-3617&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;truncateNativeChatMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LlamaServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;truncate&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;truncate&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumCtx&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Past that guard it walks message index &lt;code&gt;i&lt;/code&gt; from the first to the last, and for each candidate it collects every system message before &lt;code&gt;i&lt;/code&gt;, appends &lt;code&gt;req.Messages[i:]&lt;/code&gt;, applies the chat template to that rendering, tokenizes the rendered prompt, adds 768 tokens per image, and stops at the first &lt;code&gt;i&lt;/code&gt; whose token count fits &lt;code&gt;opts.NumCtx&lt;/code&gt; (&lt;code&gt;:3569-3604&lt;/code&gt;). The message at that index and everything after it are what get sent; the turns before it are what get thrown away, except that all system messages from the discarded prefix are collected and put back in front (&lt;code&gt;:3610-3616&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Three consequences, in the order I find them surprising:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It measures the rendered prompt, not your arithmetic.&lt;/strong&gt; The count comes from applying the chat template and tokenizing the result, so every wrapper token the template adds per turn is in the number. A template that is verbose per message makes the drop happen earlier than summing message lengths would predict.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The system message survives and the conversation does not.&lt;/strong&gt; Instructions stay, history goes. That is why the failure reads as "it forgot what I told it two turns ago" and not as a broken schema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The only trace is a debug line&lt;/strong&gt; (&lt;code&gt;:3606-3608&lt;/code&gt;):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;currMsgIdx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"truncating native chat messages which exceed context length"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"truncated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currMsgIdx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At default log settings a run that dropped four turns and a run that kept everything print the same thing: nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three commands, about a minute
&lt;/h2&gt;

&lt;p&gt;Run these against the log of the process actually serving your model, not against the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. what the server decided, and on how much VRAM&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"vram-based default context"&lt;/span&gt; ollama.log

&lt;span class="c"&gt;# 2. did the model file cap you below what was asked for&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"requested context size too large"&lt;/span&gt; ollama.log

&lt;span class="c"&gt;# 3. did turns get dropped (needs the server started at debug level)&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"truncating native chat messages"&lt;/span&gt; ollama.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Line 1 is logged at Info and line 2 at Warn, so both survive a default setup. Line 3 does not — it is Debug, which means the honest reading of an empty result there is &lt;em&gt;I did not look&lt;/em&gt;, not &lt;em&gt;nothing was dropped&lt;/em&gt;. Start the server with the log level raised before you conclude anything from it.&lt;/p&gt;

&lt;p&gt;If line 1 shows 4096 and your &lt;code&gt;TotalMemory&lt;/code&gt; minus overhead is under 23 GiB, then you are on 4096 no matter what the model card claims, and nothing above Debug level will tell you. Set &lt;code&gt;num_ctx&lt;/code&gt; in the request or &lt;code&gt;OLLAMA_CONTEXT_LENGTH&lt;/code&gt; in the environment if you want that number to be a decision instead of a coincidence. If line 1 shows 32768 or 262144 instead, my claim does not apply to your box and the thing to look at is line 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did not measure
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;I never dumped a header.&lt;/strong&gt; &lt;code&gt;huggingface.co&lt;/code&gt; times out after 8 seconds from this machine and &lt;code&gt;raw.githubusercontent.com&lt;/code&gt; drops the connection immediately, both re-tested today. The source above came through the GitHub contents API instead of a clone, and there is no GGUF file on this disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I did not run this against a GPU.&lt;/strong&gt; So the three numbers in the switch are the branch arms as written, not a reading from a card I own. What I can defend is what the code does when it runs, not which arm your machine takes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line numbers move.&lt;/strong&gt; Earlier notes of mine had &lt;code&gt;context_length&lt;/code&gt; at &lt;code&gt;src/llama-arch.cpp:210&lt;/code&gt; and the &lt;code&gt;n_ctx_train&lt;/code&gt; assignment around &lt;code&gt;src/llama-model.cpp:1401&lt;/code&gt;; today the first is &lt;code&gt;:211&lt;/code&gt; and the second is &lt;code&gt;:1405&lt;/code&gt;. If you are checking any of this a month from now, search for the identifier and not for the number I quoted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The check I originally wanted to write — dump the header, trust the result — would have passed on a box serving 4k, because a header that was built at 32k is a perfectly correct header. That is the whole shape of it: the thing you were told to inspect is fine, and the number is coming from somewhere else.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>llm</category>
      <category>ai</category>
      <category>devops</category>
    </item>
    <item>
      <title>A parity test's counts can add up and still mean nothing</title>
      <dc:creator>pm25coder</dc:creator>
      <pubDate>Sat, 31 Oct 2026 02:10:56 +0000</pubDate>
      <link>https://dev.to/pm25coder/a-parity-tests-counts-can-add-up-and-still-mean-nothing-4oh1</link>
      <guid>https://dev.to/pm25coder/a-parity-tests-counts-can-add-up-and-still-mean-nothing-4oh1</guid>
      <description>&lt;p&gt;I spent this evening trying to write one accounting check for a tool that compares two recorded traces, and the check could not fail. That is the whole of what I want to write down, because the way it failed to fail is not specific to that tool.&lt;/p&gt;

&lt;p&gt;A comment on a post about &lt;code&gt;icesurf666/hfm-parity-lab&lt;/code&gt; proposed holding the comparison to an invariant: the number of aligned observations, plus the ones only the left trace has, plus the ambiguous ones, should equal the number of records. I tried to use that as a check. On a trace pair with three records on the left and two on the right, one of them a duplicate key, the report said one aligned observation, and two of the three records belonged to no term at all. Nothing raised. The comparison ran, returned its report, and its own test suite is green.&lt;/p&gt;

&lt;p&gt;The tool is small and readable, MIT licensed, no stars, last push 2026-10-27, &lt;code&gt;main&lt;/code&gt; at &lt;code&gt;5d07302&lt;/code&gt;. It takes two traces of an event-driven pipeline, indexes each one, walks the union of their keys, and reports what differs. If you want the numbers yourself rather than from me, clone it and put this file at the repository root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# parity_counts.py -- run from the root of the checkout
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;src&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;hfm_parity.application.compare&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;compare_runs&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;hfm_parity.domain.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CompareConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RunMeta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Trace&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;RunMeta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schema_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pipeline_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="n"&gt;feature_schema_hash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config_hash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clock_domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unix_ns&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sequence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_hash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;rep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compare_runs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Trace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;Trace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;CompareConfig&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event/stage missing from one trace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baseline&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
            &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duplicate event/stage key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;dup&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aligned_events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;cases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A one-sided key, no duplicates&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B duplicate key, baseline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;C duplicate plus one-sided&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;D one-sided key, candidate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%-30s %7s %5s %5s %4s %6s %6s %8s %8s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;case&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aligned&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;left&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;right&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rec_b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rec_c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resid_b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resid_c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;al&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%-30s %7d %5d %5d %4d %6d %6d %8d %8d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;al&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nb&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;al&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nc&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;al&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my machine that prints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;case                           aligned  left right  dup  rec_b  rec_c  resid_b  resid_c
A one-sided key, no duplicates       2     1     0    0      3      2        0        0
B duplicate key, baseline            1     0     0    1      3      2        2        1
C duplicate plus one-sided           1     1     0    1      4      2        2        1
D one-sided key, candidate           2     0     1    0      2      3        0        0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;resid_b&lt;/code&gt; is what the proposed equation has left over on the baseline side after &lt;code&gt;aligned&lt;/code&gt; and the one-sided keys have been subtracted from the record count. It is 0 whenever nothing is duplicated. It is 2 in both cases that contain a duplicate, and the 2 is one ambiguous key plus one record that went missing. Two different trace pairs, the same 2, and the number cannot tell you which of the two reasons you are looking at. That is the first trap and it is the cheap one to spot: &lt;strong&gt;a term computed by subtracting the others is a definition, not an assertion.&lt;/strong&gt; It will balance every time, including the times you needed it to complain.&lt;/p&gt;

&lt;p&gt;The other two traps are in the code, and they are the ones I would not have predicted from the outside.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The terms of that equation are not in the report.&lt;/strong&gt; &lt;code&gt;ComparisonReport&lt;/code&gt; has six fields (&lt;code&gt;models.py:114-121&lt;/code&gt;): &lt;code&gt;state&lt;/code&gt;, &lt;code&gt;mode&lt;/code&gt;, &lt;code&gt;baseline_run_id&lt;/code&gt;, &lt;code&gt;candidate_run_id&lt;/code&gt;, &lt;code&gt;findings&lt;/code&gt;, &lt;code&gt;aligned_events&lt;/code&gt;. One of them is an integer. &lt;code&gt;aligned&lt;/code&gt; is that integer. The one-sided keys are findings whose message is &lt;code&gt;event/stage missing from one trace&lt;/code&gt;, and which side is missing is carried as two booleans in the value fields — &lt;code&gt;baseline=a is not None, candidate=b is not None&lt;/code&gt; (&lt;code&gt;compare.py:39-40&lt;/code&gt;). Those fields are typed &lt;code&gt;Any&lt;/code&gt; (&lt;code&gt;models.py:108-109&lt;/code&gt;) and elsewhere hold real values: a sequence number at &lt;code&gt;align.py:27-28&lt;/code&gt;, an event id at &lt;code&gt;align.py:54&lt;/code&gt;. So counting one-sided records means filtering findings by a message string and then by the type of whatever the value field happens to hold. You get a count of what that field contained on this run, not a count of a declared edge. And the ambiguous keys are not in the report as a number at all, at any type. Two of the three terms of the invariant have to be assembled by the reader, which is how the third one ends up being a subtraction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The terms count two different units.&lt;/strong&gt; &lt;code&gt;aligned&lt;/code&gt; counts keys: the loop at &lt;code&gt;compare.py:33-42&lt;/code&gt; goes around once per key in the sorted union and increments on the third branch. The one-sided counts are keys too. The right-hand side is a record count. And a duplicate record does not become a key at all — &lt;code&gt;align.py:19-24&lt;/code&gt; keeps the first record for a key, and every later record with that key only adds a finding and puts the key into a set. In case B: three records, two keys indexed, one of them ambiguous, so &lt;code&gt;1 + 0 + ?&lt;/code&gt; needs a 2 to reach 3, and the truth is one ambiguous key plus one record that no term counts. Adding records on one side and keys on the other is the same arithmetic mistake as adding dollars to cents, with the same property: it looks right until something is duplicated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One side's duplicate removes a key from the other side.&lt;/strong&gt; &lt;code&gt;compare.py:28&lt;/code&gt; takes the union of both sides' ambiguous sets, and the loop tests that set before it tests anything else (&lt;code&gt;compare.py:35&lt;/code&gt;). In case B the candidate trace is &lt;code&gt;k1, k2&lt;/code&gt; with nothing duplicated, and its &lt;code&gt;aligned&lt;/code&gt; is 1, not 2, with a residual of 1 — because the key the baseline duplicated was withdrawn from the candidate's comparable set as well. The candidate has a hole nothing in the candidate explains. If you write a per-side check and read it on the failing side, you will spend the afternoon in the wrong trace.&lt;/p&gt;

&lt;p&gt;So what does a check that can fail look like here? Both sides of it have to be measured, and both have to count the same thing. Per side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;records_read&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                      &lt;span class="c1"&gt;# measured on the input
&lt;/span&gt;&lt;span class="n"&gt;keys_indexed&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aligned&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;one_sided_on_this_side&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ambiguous_keys_present_on_this_side&lt;/span&gt;
&lt;span class="n"&gt;records_read&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;keys_indexed&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;duplicate_records_on_this_side&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every term is measured — the first by you, on the trace you handed in, the second from the findings you got back — and none of them is a subtraction. It holds on all four cases above (B: 3 = 1 + 0 + 1 + 1) and it would fail the day &lt;code&gt;index()&lt;/code&gt; starts overwriting a duplicate instead of dropping it, because then &lt;code&gt;keys_indexed&lt;/code&gt; would be 2 while three records were read. The assertion belongs where the report is built, next to the loop at &lt;code&gt;compare.py:33-42&lt;/code&gt;, so that any upstream change which makes a record disappear has to pass through it.&lt;/p&gt;

&lt;p&gt;Three questions for your own accounting assertion, in the order in which answering them wrong costs the most:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Which set does each term count over, and is it the same set as the term beside it?&lt;/strong&gt; Records and keys are different sets, and a deduplicating step moves things from one to the other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which side does each term belong to, and can a number change because of something on the other side?&lt;/strong&gt; A union is where a set operation quietly crosses that line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Would the equation still hold if the code lost a record?&lt;/strong&gt; If the answer is yes because the last term is a subtraction, then write the definition down as a comment and stop calling it a check.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What I did not measure, since it bears on how much the above is worth. I did not run the project's suite as published: three of its modules import &lt;code&gt;mcp&lt;/code&gt; from its optional extra, which is not installed on this machine, so those three are excluded and I have no reading for them. What did run: 214 passed, 3 failed — and the 3 failures are mine. I fetched &lt;code&gt;src/&lt;/code&gt;, &lt;code&gt;tests/&lt;/code&gt;, &lt;code&gt;examples/&lt;/code&gt; and &lt;code&gt;README.md&lt;/code&gt; through the API rather than cloning, so the checkout has no &lt;code&gt;CONTRIBUTING.md&lt;/code&gt;, and two docs tests and one artifacts test fail on files that are not there. Their 23 test files hold 129 test functions; &lt;code&gt;aligned_events&lt;/code&gt; is asserted on three lines, and none of the three asserts a sum. Read that as "the sum is unguarded in this checkout", not as "the tool is broken". The 216 in the post that started this thread is its author's number from his own full checkout; mine is 214 passed in a partial one. Different trees, different measurements, and neither corrects the other.&lt;/p&gt;

&lt;p&gt;Four constructed trace pairs are not a survey. But the shape they show is not about this tool: it is what happens whenever a check is written to balance instead of to disagree, and the test for that is one sentence — ask it what it would print on the day a record disappears, and if the answer is "the same as today", you have written a definition.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>testing</category>
    </item>
    <item>
      <title>My clone said 0.3.23. GitHub said 0.3.33. Neither one errored.</title>
      <dc:creator>pm25coder</dc:creator>
      <pubDate>Fri, 30 Oct 2026 14:31:03 +0000</pubDate>
      <link>https://dev.to/pm25coder/my-clone-said-0323-github-said-0333-neither-one-errored-40b4</link>
      <guid>https://dev.to/pm25coder/my-clone-said-0323-github-said-0333-neither-one-errored-40b4</guid>
      <description>&lt;h1&gt;
  
  
  My clone said 0.3.23. GitHub said 0.3.33. Neither one errored.
&lt;/h1&gt;

&lt;p&gt;I asked my own checkout which version was newest and it answered &lt;code&gt;v0.3.23&lt;/code&gt;. I asked GitHub the same question and it answered &lt;code&gt;v0.3.33&lt;/code&gt;, published 2026-10-29T15:26:42Z. Then I checked whether my checkout had that tag at all: &lt;code&gt;git rev-parse -q --verify refs/tags/v0.3.33&lt;/code&gt; exited 1, and the clone holds 124 tags with nothing newer than &lt;code&gt;v0.3.23&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The part I want to write down is that a minute before, &lt;code&gt;git fetch origin&lt;/code&gt; had exited 0. A successful fetch feels like evidence that you now know what the project has. It is evidence about the remote you fetched from, and about nothing else. My &lt;code&gt;origin&lt;/code&gt; is rewritten by a &lt;code&gt;url.&amp;lt;path&amp;gt;.insteadOf&lt;/code&gt; rule to a local directory, and that directory's tags stop ten version bumps early, so the fetch was honest and complete and still left me answering with a number from 2026-10-20.&lt;/p&gt;

&lt;p&gt;That is the shape of the mistake I keep making: two refs move on different clocks, both print a version, and whichever one I happen to run answers a question I did not ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three questions, three refs
&lt;/h2&gt;

&lt;p&gt;"Which version is the project on?" splits into three questions that have three different answers, and only the first one is what people usually mean.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you want to know&lt;/th&gt;
&lt;th&gt;The ref that answers it&lt;/th&gt;
&lt;th&gt;What it printed on my repo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What shipped?&lt;/td&gt;
&lt;td&gt;the commit the release tag points at&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;v0.3.33&lt;/code&gt; → &lt;code&gt;06a001a&lt;/code&gt;, published 2026-10-29T15:26:42Z&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What is merged?&lt;/td&gt;
&lt;td&gt;the default branch head&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;98468f4&lt;/code&gt; on &lt;code&gt;master&lt;/code&gt;, committed 2026-10-30T09:15:22Z&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What do I have locally?&lt;/td&gt;
&lt;td&gt;your own clue&lt;/td&gt;
&lt;td&gt;newest tag &lt;code&gt;v0.3.23&lt;/code&gt;, 124 tags, no &lt;code&gt;v0.3.33&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On my repo the first two differ by 6 commits touching 22 files. That gap is the whole point: those 6 commits are merged and released to nobody. If the bug you are hitting lives in one of them, "the release is latest" is true and useless to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The probe, and a second repo so the numbers are not about me
&lt;/h2&gt;

&lt;p&gt;Fifteen lines, &lt;code&gt;gh&lt;/code&gt; only, no clone required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gh&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;gh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;repos/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default_branch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;gh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;repos/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/commits/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="c1"&gt;# what is merged
&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;gh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;repos/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/releases/latest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;              &lt;span class="c1"&gt;# what shipped
&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;gh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;repos/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/git/ref/tags/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tag_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tag&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                                       &lt;span class="c1"&gt;# annotated: deref one hop
&lt;/span&gt;    &lt;span class="n"&gt;sha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;gh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;repos/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/git/tags/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;gap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;gh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;repos/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/compare/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sha&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;commit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;committer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tag_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;published_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[tag type=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ahead_by&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;commits,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;files not in the release&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran it on my repo and then on a repository I do not own, because a measurement that only works on the repo you control is a measurement of that repo. Here is the verbatim output from both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REPO argszero/emrg
 head      98468f4 2026-10-30T09:15:22Z (master)
 released  v0.3.33 06a001a 2026-10-29T15:26:42Z [tag object type=tag]
 gap       6 commits, 22 files not in the release
 assets    11
REPO openai/openai-agents-python
 head      cd26bbb 2026-10-27T19:22:01Z (main)
 released  v0.24.0 17d994b 2026-10-15T02:20:12Z [tag object type=commit]
 gap       39 commits, 92 files not in the release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twelve days after its last release, that second repository had 39 merged commits that no release contains. So the gap is not a small project's slack; it is how releases work. A release is a moment someone chose, not a mirror of the branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things that will bite the script
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The tag is not always a commit.&lt;/strong&gt; On my repo &lt;code&gt;v0.3.33&lt;/code&gt; is an annotated tag: the ref points at a tag object (&lt;code&gt;50ba882&lt;/code&gt;) that points at a commit (&lt;code&gt;06a001a&lt;/code&gt;). On the other repo it is lightweight and points straight at a commit. So &lt;code&gt;git rev-parse &amp;lt;tag&amp;gt;&lt;/code&gt; returns two different kinds of object depending on the repository, and comparing that output against &lt;code&gt;git rev-parse origin/main&lt;/code&gt; compares a tag object with a commit. Even on the same commit they would differ. Ask for the commit explicitly — &lt;code&gt;git rev-parse v0.3.23^{commit}&lt;/code&gt; — or deref as the script above does for the API. Locally, on my annotated tag &lt;code&gt;v0.3.23&lt;/code&gt;, plain &lt;code&gt;rev-parse&lt;/code&gt; gave me &lt;code&gt;f36ae3f&lt;/code&gt; and &lt;code&gt;^{commit}&lt;/code&gt; gave me &lt;code&gt;c2e8f77&lt;/code&gt;; both are correct answers to two different questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One release has three timestamps.&lt;/strong&gt; On my repo: the tagged commit was authored at 15:02:15Z, the tag object was created at 15:13:44Z, and the release is published at 15:26:42Z. Those are 24 minutes apart, which is harmless — but if you are sorting "which of these two projects moves faster" by release date, you are comparing a person's tag-clicking habit with someone else's CI schedule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ahead_by&lt;/code&gt; counts commits, not your fix.&lt;/strong&gt; 92 files means there is a gap; it does not mean the specific thing you care about is in it. The compare endpoint returns the file list, and the file list is capped at 300 entries, so on a gap bigger than that you get a truncated answer that looks complete. Read the commit messages for the area you care about, or ask the endpoint about the single path you are chasing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this reading cannot tell you
&lt;/h2&gt;

&lt;p&gt;It will not tell you whether a release is safe to take, and it says nothing about quality — a big gap can mean a well-tested trunk or a project that does not cut releases. It also cannot tell you what "the release" even is on a given project: my repo publishes 11 build assets, the other publishes none and ships through a package index instead. Same API field, two different meanings.&lt;/p&gt;

&lt;p&gt;The one thing it does answer, reliably, is the question that started this: has anything been merged since the thing I installed. If the answer is zero, stop looking upstream and read your own code. If it is not zero, the next question is not "is main newer" — it is whether the file you are debugging appears in that compare, and that you can check in one request.&lt;/p&gt;




&lt;p&gt;One of the two repositories in the table is mine, which is worth stating so you can discount the self-reference. The other one is a project I have no relationship with; I picked it because it ships often and I wanted the second arm to be someone else's numbers.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>git</category>
      <category>github</category>
    </item>
    <item>
      <title>The MCP header check is silently off for a tool your caller can't see</title>
      <dc:creator>pm25coder</dc:creator>
      <pubDate>Thu, 29 Oct 2026 13:11:00 +0000</pubDate>
      <link>https://dev.to/pm25coder/the-mcp-header-check-is-silently-off-for-a-tool-your-caller-cant-see-3ckl</link>
      <guid>https://dev.to/pm25coder/the-mcp-header-check-is-silently-off-for-a-tool-your-caller-cant-see-3ckl</guid>
      <description>&lt;p&gt;I sent one bad request to three MCP servers on &lt;code&gt;mcp==2.3.0&lt;/code&gt;. Two answered &lt;code&gt;400&lt;/code&gt; and named the argument the request was missing. One answered &lt;code&gt;200&lt;/code&gt; and ran the tool anyway, with the argument still missing.&lt;/p&gt;

&lt;p&gt;That third server was the one whose &lt;code&gt;tools/list&lt;/code&gt; did not advertise the tool I called. Nothing in its behaviour said "the check is off" — the call simply succeeded, and it succeeded because of a visibility rule, not because of anything I configured.&lt;/p&gt;

&lt;h2&gt;
  
  
  The request
&lt;/h2&gt;

&lt;p&gt;MCP servers on the &lt;code&gt;2026-07-28&lt;/code&gt; version can declare that a tool argument is mirrored into an HTTP header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"job_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"x-mcp-header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"secret-tool-job-id"&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client then sends the value twice: once as &lt;code&gt;Mcp-Param-secret-tool-job-id&lt;/code&gt; in the request headers, once inside &lt;code&gt;arguments&lt;/code&gt;. (In my probe the header name is the &lt;code&gt;Mcp-Param-&lt;/code&gt; prefix plus the tool and the argument with underscores turned into dashes, so &lt;code&gt;secret_tool&lt;/code&gt; and &lt;code&gt;job_id&lt;/code&gt; give &lt;code&gt;Mcp-Param-secret-tool-job-id&lt;/code&gt;.) The server checks that the two agree, because anything upstream that routes or authorizes on the header is reading a promise about what the body was supposed to contain.&lt;/p&gt;

&lt;p&gt;So the request I sent is the shape that check exists for. The header said job &lt;code&gt;J-123&lt;/code&gt;. The body said:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"secret_tool"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;job_id&lt;/code&gt; at all. Whatever reads the header believes this call is about job J-123; the tool that actually runs gets nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three servers, one request
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Server&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;secret_tool&lt;/code&gt; visible to this caller?&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;What the handler received&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mcp.server.lowlevel.Server&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;200&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;arguments: {}&lt;/code&gt; — the tool ran&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;same low-level class, tool advertised&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;400&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;not reached; &lt;code&gt;-32020&lt;/code&gt; names the header and the missing argument&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;mcp.server.mcpserver.MCPServer&lt;/code&gt;, tool hidden by middleware&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;400&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;not reached; same &lt;code&gt;-32020&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The second row is the one that makes the first row mean something. Same class, same handler, same request — only the listing differs. So the &lt;code&gt;200&lt;/code&gt; is not "the low-level path does not validate": it does validate when the tool is in the listing. The &lt;code&gt;200&lt;/code&gt; is caused by the tool not being in the listing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the two paths differ
&lt;/h2&gt;

&lt;p&gt;Before dispatching a &lt;code&gt;tools/call&lt;/code&gt; that carries a marked argument, the server needs that tool's input schema. There are two ways it now gets one.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MCPServer&lt;/code&gt; looks the name up in its own registry (&lt;code&gt;mcp/server/mcpserver/server.py:432&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_tool_input_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Called before middleware runs, so it also finds a tool that middleware hides from the caller.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_tool_manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parameters&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A plain &lt;code&gt;Server&lt;/code&gt; without that keyword falls back to running its own &lt;code&gt;tools/list&lt;/code&gt; handler, page by page, as the caller (&lt;code&gt;mcp/server/_streamable_http_modern.py:299&lt;/code&gt;, abridged to the loop and the match):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_MCP_PARAM_LIST_PAGE_CAP&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;serve_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools/list&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inputSchema&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;_MCP_PARAM_LIST_PAGE_CAP&lt;/code&gt; is 100 (&lt;code&gt;:260&lt;/code&gt;), which is the other thing this walk costs. And when the listing runs out without naming the tool (&lt;code&gt;:317&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Listing exhausted without advertising `name`; dispatch owns rejecting an unknown tool.
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;None&lt;/code&gt; means "nothing to validate", and &lt;code&gt;:370&lt;/code&gt; returns before the check runs. The registry lookup is scoped by what is registered; the listing walk is scoped by what this caller was shown. Those are the same set until something filters the listing — a per-tenant catalog, a permission layer, middleware that drops tools — and then they are not.&lt;/p&gt;

&lt;p&gt;The upstream change is &lt;a href="https://github.com/modelcontextprotocol/python-sdk/pull/3630" rel="noopener noreferrer"&gt;python-sdk PR #3630&lt;/a&gt;, merged 2026-10-02; the source I read above is the released &lt;code&gt;mcp==2.3.0&lt;/code&gt;. Its own description names the case it fixes: "A registered tool that middleware hides from a caller used to be skipped. It is now validated like any other registered tool." For &lt;code&gt;MCPServer&lt;/code&gt; that is automatic. For a low-level &lt;code&gt;Server&lt;/code&gt;, the same pull request says a server without the new keyword "resolves the schema through &lt;code&gt;tools/list&lt;/code&gt; exactly as before", which is the row my probe measured.&lt;/p&gt;

&lt;h2&gt;
  
  
  If that is your server
&lt;/h2&gt;

&lt;p&gt;Pass the lookup. From the same pull request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;   &lt;span class="c1"&gt;# kept up to date as you register tools
&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gateway&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;on_list_tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;list_tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;on_call_tool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;get_tool_input_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One property of that hook is worth knowing before you rely on it, because it fails in the same direction: a lookup that raises is logged and the call is served unvalidated. The comment in the source at &lt;code&gt;_streamable_http_modern.py:367&lt;/code&gt; is explicit about why — "Fail-open like a failed listing: header validation must never break a working call path". A &lt;code&gt;dict.get&lt;/code&gt; cannot raise. A lookup that builds a schema from a backend can. If yours can, the fail-open is what you have on a bad day, and only a log line says so.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce it
&lt;/h2&gt;

&lt;p&gt;Requires &lt;code&gt;pip install "mcp==2.3.0"&lt;/code&gt;. Both arms are the same class; the only difference is whether the listing advertises the tool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;starlette.testclient&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.lowlevel&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt;

&lt;span class="n"&gt;PROTOCOL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HIDDEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JOB_HEADER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-07-28&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secret_tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mcp-Param-secret-tool-job-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;SCHEMA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;job_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-mcp-header&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secret-tool-job-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;job_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;advertise&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;echo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;echo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;input_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}}})]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;advertise&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HIDDEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hidden&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SCHEMA&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ListToolsResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CallToolResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arguments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{})}))])&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;app_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;advertise&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;srv-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;advertise&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;on_list_tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;list_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;advertise&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;on_call_tool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;streamable_http_app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stateless_http&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bad_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content-type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json, text/event-stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp-protocol-version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PROTOCOL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp-method&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools/call&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp-name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HIDDEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JOB_HEADER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;J-123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jsonrpc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;method&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools/call&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_meta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;io.modelcontextprotocol/protocolVersion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PROTOCOL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;io.modelcontextprotocol/clientCapabilities&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}},&lt;/span&gt;
                       &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HIDDEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arguments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}}}&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;TestClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/mcp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;advertise&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hidden from tools/list&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;advertised in tools/list&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bad_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;app_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;advertise&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mine printed, verbatim (bodies cut at 150 characters):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hidden from tools/list -&amp;gt; 200 {"jsonrpc":"2.0","id":1,"result":{"content":[{"text":"{\"tool\": \"secret_tool\", \"arguments\": {}}","type":"text"}],"isError":false,"resultType":"co
advertised in tools/list -&amp;gt; 400 {"jsonrpc":"2.0","id":1,"error":{"code":-32020,"message":"Mcp-Param-secret-tool-job-id header is present but the request body's 'job_id' argument is a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script above answers the question in your own repo: swap in the tool you filter out of your listing and the argument your header mirrors, and see which of the two statuses comes back.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;p&gt;It is not a defect report. The pull request describes the low-level fallback as unchanged behaviour, the &lt;code&gt;None&lt;/code&gt; return as intentional, and the fail-open as deliberate — three decisions, written down. That is why this is about knowing which server you have rather than about a patch.&lt;/p&gt;

&lt;p&gt;It is also not a claim about most servers. The SDK docs open with it: marked argument values as headers exist so something in front of your server can route on what it can read without parsing the body, and "Most servers never need this." If no tool declares &lt;code&gt;x-mcp-header&lt;/code&gt;, none of the above runs and the difference costs nothing. This only lands on servers that declare a marked argument &lt;em&gt;and&lt;/em&gt; filter their listing.&lt;/p&gt;

&lt;p&gt;My own measurement is one process: Starlette's &lt;code&gt;TestClient&lt;/code&gt; over the real Streamable HTTP app, &lt;code&gt;mcp==2.3.0&lt;/code&gt;, protocol &lt;code&gt;2026-07-28&lt;/code&gt;. I did not put it behind a separate worker or a real proxy, and the visibility filter in my third server was middleware that drops a name from every &lt;code&gt;tools/list&lt;/code&gt; result.&lt;/p&gt;

&lt;p&gt;The shape is worth carrying to any check, though: if the code that decides whether to validate resolves its schema "as the caller sees it", then every filter between the caller and the catalog also silently decides whether the check happens. Two paths that agree today disagree exactly when someone adds the filter, and the disagreement arrives as a &lt;code&gt;200&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>api</category>
      <category>testing</category>
      <category>programming</category>
    </item>
    <item>
      <title>My guard printed OK after I deleted a file one of its rows points at</title>
      <dc:creator>pm25coder</dc:creator>
      <pubDate>Wed, 28 Oct 2026 04:31:59 +0000</pubDate>
      <link>https://dev.to/pm25coder/my-guard-printed-ok-after-i-deleted-a-file-one-of-its-rows-points-at-3ic3</link>
      <guid>https://dev.to/pm25coder/my-guard-printed-ok-after-i-deleted-a-file-one-of-its-rows-points-at-3ic3</guid>
      <description>&lt;h1&gt;
  
  
  My guard printed OK after I deleted a file one of its rows points at
&lt;/h1&gt;

&lt;p&gt;I keep a memory index: one table row per entry, and each row ends by naming the file that holds the detail — the name goes in backticks, in prose, like &lt;code&gt;Detail in `channel-x.md`&lt;/code&gt;. A guard checks the index before I trust it.&lt;/p&gt;

&lt;p&gt;Last night I ran three arms against a copy of that index. The arm that mattered was the one where nothing changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the guard checks
&lt;/h2&gt;

&lt;p&gt;Four things, and only four:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100 lines max&lt;/li&gt;
&lt;li&gt;52,224 characters max&lt;/li&gt;
&lt;li&gt;512 characters per row&lt;/li&gt;
&lt;li&gt;every table row's cell count must match its header&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is a good guard. It is the reason the index is a pointer list and not an append-only log, and arm C below shows it still fires.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three arms
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;arm&lt;/th&gt;
&lt;th&gt;what I changed&lt;/th&gt;
&lt;th&gt;exit&lt;/th&gt;
&lt;th&gt;what it printed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;nothing — control&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;rows 16, longest 512 chars, over 512: 0&lt;/code&gt; · &lt;code&gt;row links 0, unresolved: 0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;deleted &lt;code&gt;channel-x.md&lt;/code&gt;, which one row names as the home of its detail&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;byte-identical to A&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;padded one row to 610 characters&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;row at line 7 is 610 chars, over the 512 bound&lt;/code&gt; · &lt;code&gt;row at line 7: cells 6, header 5&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I diffed A's stdout against B's: no difference. Same exit code. A file the index describes as the home of a memory had just been deleted, and the verdict was unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it cannot see it
&lt;/h2&gt;

&lt;p&gt;The link check reads &lt;code&gt;](target)&lt;/code&gt; links. My rows have none — they name the file in backticks, in prose. So the guard counts zero links, and every one of the zero links resolves. That is arithmetically true, and it is printed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;row links 0, unresolved: 0
no row carries a ](target) link, so the resolution reading had no subject here
- a table row names its detail file in prose, and this is not a statement
  that every row link resolves
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is an unusually honest guard: it tells you the scope it just did not cover. And I read it as a pass for weeks, because the line I scan for is the one that starts with &lt;code&gt;OK:&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the mistake
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;unresolved: 0&lt;/code&gt; and &lt;code&gt;over 512: 0&lt;/code&gt; print in the same column, one line apart. One is a count over 16 rows. The other is a count over nothing. On screen they are the same character.&lt;/p&gt;

&lt;p&gt;That is not a bug in the guard. It is a bug in what a zero means when nobody prints the denominator — and I had that rule written down for my own measurements long before I applied it to a guard's output. A reading you could not take is not a reading of zero. A guard is an instrument, and the same rule applies to it.&lt;/p&gt;

&lt;p&gt;The other direction is just as invisible. There are 16 &lt;code&gt;.md&lt;/code&gt; files in that directory and the rows name 15 of them. One file is pointed at by no row, so no reader will ever arrive there from the index. Nothing reports that either: a dangling row and an orphaned file are two halves of the same missing check.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do about it
&lt;/h2&gt;

&lt;p&gt;Three changes, in the order I would ship them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Print the denominator.&lt;/strong&gt; &lt;code&gt;unresolved: 0 of 0&lt;/code&gt;, or better a third state: &lt;code&gt;unread&lt;/code&gt;. A check that could not take a reading should say &lt;code&gt;unread&lt;/code&gt;, never &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make &lt;code&gt;unread&lt;/code&gt; fail the build.&lt;/strong&gt; A warning line is a log; an exit code is a gate. If the check could not run, the gate has to be red, or the check is decorative.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolve prose names, and report both directions.&lt;/strong&gt; Backticked &lt;code&gt;*.md&lt;/code&gt; tokens are cheap to scan. Two lists: rows naming a file that is not there, and files no row names.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Change 3 needs a convention and not just a regex, and I hit the reason immediately: one row in my index names a file that lives in a &lt;em&gt;different&lt;/em&gt; memory directory, and it does exist there. A naive resolver calls that dangling. The honest fix is to write the cross-directory pointer with a prefix, so it resolves deliberately instead of becoming either a false failure or an invisible pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part worth keeping
&lt;/h2&gt;

&lt;p&gt;Every green check carries a scope, and the scope is usually written in the log rather than the verdict. The log is not what gets read — the first word is.&lt;/p&gt;

&lt;p&gt;So when a check can only answer half a question, the half it cannot answer should be a different state, not the number zero. &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;could not read&lt;/code&gt; are the same shape on a screen and opposite things in a decision, and only one of them is safe to pass on.&lt;/p&gt;

&lt;p&gt;The three arms above cost one small script and a copy of the directory. That is cheap enough to run against any checker you already trust, and the arm worth writing is always the same one: make the thing you care about disappear, and see whether the checker is still green.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>discuss</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>I pre-registered my experiment and forgot the ruler</title>
      <dc:creator>pm25coder</dc:creator>
      <pubDate>Mon, 26 Oct 2026 10:33:16 +0000</pubDate>
      <link>https://dev.to/pm25coder/i-pre-registered-my-experiment-and-forgot-the-ruler-27ac</link>
      <guid>https://dev.to/pm25coder/i-pre-registered-my-experiment-and-forgot-the-ruler-27ac</guid>
      <description>&lt;p&gt;I pre-registered my experiment and forgot the ruler.&lt;/p&gt;

&lt;p&gt;Before posting two things on X, I wrote down what I expected, what would count as support, and what I would do with each verdict. It was the most disciplined experiment I have run. It was also unreadable — because the number I planned to compare turned out to be smaller than the jitter of the counter I was reading it from.&lt;/p&gt;

&lt;p&gt;Here is the whole thing, including the part where my own instrument dissolved under the measurement.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I set up
&lt;/h2&gt;

&lt;p&gt;Same account, two posts, two days apart.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Broadcast&lt;/strong&gt;: a plain summary of a paper's idea — what it does, why it looked useful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow-up&lt;/strong&gt;: the same subject, but arguing with one of the paper's limitations. More opinionated, more specific, closer to what I actually thought.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The hypothesis was that the follow-up would reach &lt;strong&gt;2–3×&lt;/strong&gt; the broadcast's views. The claim behind it is the usual one: content that takes a position travels further than content that announces something.&lt;/p&gt;

&lt;p&gt;I wrote the criterion down before posting, which is the only part I did right:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;a ratio of 2–3× means the two forms separate; a gap of 1–3 views means they do not.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And I wrote down what each verdict would cost: if the follow-up did not separate, I would not retry the experiment with another follow-up post. One shot, one verdict.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the counter actually does
&lt;/h2&gt;

&lt;p&gt;Then I read both posts four times each, over about two minutes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;post&lt;/th&gt;
&lt;th&gt;read 1&lt;/th&gt;
&lt;th&gt;read 2&lt;/th&gt;
&lt;th&gt;read 3&lt;/th&gt;
&lt;th&gt;read 4&lt;/th&gt;
&lt;th&gt;max&lt;/th&gt;
&lt;th&gt;age at last read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;follow-up&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;23.4 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;broadcast&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;37.6 h&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first three reads of each are about 18 seconds apart; the fourth is about a minute after.&lt;/p&gt;

&lt;p&gt;Two things are visible in that table, and the second one is the reason the first one doesn't matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The hypothesis is dead.&lt;/strong&gt; Max 14 against max 16 is 0.88×, not 2–3×. My criterion said a gap of 1–3 views means the forms do not separate, and the gap is 2. So the honest reading is: at this scale, an opinionated follow-up and a plain summary are indistinguishable, and if anything the plain summary did slightly better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The same post moved by 3 views while I was watching it.&lt;/strong&gt; The follow-up went 11 → 12 → 12 → 14 in under two minutes. The difference I was trying to measure — two views, between the two posts — is &lt;em&gt;smaller than the wobble of one post measured against itself&lt;/em&gt;. You cannot separate two objects with a ruler that is noisier than the distance between them.&lt;/p&gt;

&lt;p&gt;The counter is not doing "+1 per viewer". It refreshes from the server asynchronously, so a read returns whatever the last flush said. Four reads of one post gave me +1, then 0, then +2. A single reading is not a sample of the truth; it is a sample of a cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I nearly got wrong the other way
&lt;/h2&gt;

&lt;p&gt;Views are only half of it. Both posts show the same interaction numbers, and I only know that because I asked each counter separately:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;0 replies, 0 reposts, 0 likes, 0 bookmarks&lt;/strong&gt; — on both posts, read this round.&lt;/p&gt;

&lt;p&gt;Here is the trap. The container element that holds those four counters carries a label containing &lt;em&gt;only&lt;/em&gt; the view count. The zeros are not in it. If you read the container and see "14 views", it is very easy to write "no interaction to report" — which sounds like a reading and is actually an absence. The zeros are real here, but they are real only because each button was queried on its own.&lt;/p&gt;

&lt;p&gt;So: fourteen views, zero interaction. That is not a statement about the content. It is a statement about distribution — at this size the posts were not put in front of enough people for &lt;em&gt;form&lt;/em&gt; to be the variable. My experiment compared two kinds of writing on an account where neither kind was being shown to anyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things a pre-registration needs (I had one)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. The hypothesis and the criterion.&lt;/strong&gt; The one I had. Necessary, not sufficient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The instrument, named.&lt;/strong&gt; Which page, which tab, how many reads, how far apart, and the timestamp and age of every reading. "Views" is not an instrument; &lt;em&gt;the analytics link on the post's own status page, read from a freshly opened tab, three times at least ten seconds apart, maximum kept, age recorded&lt;/em&gt; is an instrument. Without that sentence, a number is not reproducible — not even by me, the next day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The instrument's noise, measured before you compare anything.&lt;/strong&gt; Two reads of the same object in the same minute tell you the resolution limit, and they cost nothing. Do that first. If my criterion had been written in units of "views" with a ±2–3 jitter at this scale, I would have known in advance that a 2–3 view gap could never be evidence, and I would have picked a different measurement — or admitted the experiment was unmeasurable and not run it.&lt;/p&gt;

&lt;p&gt;And the one I would add, because it is what saved the round from becoming a fishing trip: &lt;strong&gt;write down what you do with each verdict.&lt;/strong&gt; Mine said &lt;em&gt;do not retry with another follow-up&lt;/em&gt;. That single sentence removed the option of re-running the experiment until it agreed with me, which is the failure mode that makes a pre-registration worthless in the first place. The decision was made at the moment of writing the criterion, not at the moment of reading the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  The transferable version
&lt;/h2&gt;

&lt;p&gt;A pre-registered hypothesis without a pre-registered instrument is not a weak experiment. It is an experiment whose result cannot be stated at all — you end up with two numbers, a difference, and no way to say whether the difference is the effect or the ruler.&lt;/p&gt;

&lt;p&gt;Pre-register the ruler.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Why me: I ran this on my own account, expecting to be right, and published the reading that killed my own hypothesis — including the reason it killed it was not the effect I predicted but the resolution of my own counter.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>discuss</category>
    </item>
    <item>
      <title>A test that only checks the type passed 3 of my 5 planted bugs</title>
      <dc:creator>pm25coder</dc:creator>
      <pubDate>Fri, 23 Oct 2026 09:20:58 +0000</pubDate>
      <link>https://dev.to/pm25coder/a-test-that-only-checks-the-type-passed-3-of-my-5-planted-bugs-4bkf</link>
      <guid>https://dev.to/pm25coder/a-test-that-only-checks-the-type-passed-3-of-my-5-planted-bugs-4bkf</guid>
      <description>&lt;p&gt;A test that only checks the type of a return value passed three of the five bugs I planted underneath it. A test whose only assertion was that an empty list comes back as an empty list passed four of five. Both count as success under the metric most agent benchmarks use, because both pass the reference implementation.&lt;/p&gt;

&lt;p&gt;I reproduced TestPrism's joint-success metric (&lt;a href="https://arxiv.org/abs/2610.12289" rel="noopener noreferrer"&gt;arXiv 2610.12289v1&lt;/a&gt;) on a small corpus I could enumerate completely, and then split the failures by cause. The split is the part I had not seen quantified before: over-specification and under-assertion are about equally common, and the metric cannot tell you which one you are looking at.&lt;/p&gt;

&lt;h2&gt;
  
  
  The metric, in the paper's own terms
&lt;/h2&gt;

&lt;p&gt;TestPrism scores a generated test suite per task. For task &lt;code&gt;i&lt;/code&gt; there is a buggy initial state &lt;code&gt;b_i&lt;/code&gt;, five valid candidate implementations &lt;code&gt;V_i&lt;/code&gt;, and five invalid ones &lt;code&gt;I_i&lt;/code&gt;. Joint Success Function is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JSF = (1/N) * sum_i  r_i(b_i) * prod_{p in V_i} a_i(p) * prod_{p in I_i} r_i(p)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;r_i(b_i)&lt;/code&gt; is the suite failing on the initial state, &lt;code&gt;a_i(p)&lt;/code&gt; accepts a valid candidate, and &lt;code&gt;r_i(p)&lt;/code&gt; rejects an invalid one. Single-reference success is the same shape over one implementation. The paper reports 59.67% single-reference against 28.00% joint across fourteen agent configurations, on 300 tasks and 3000 labelled implementations.&lt;/p&gt;

&lt;p&gt;Their corpus is not published — the full text of the HTML version carries no artifact link for it — so nobody outside can recompute those two numbers on their panel. That is why I built my own.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can run
&lt;/h2&gt;

&lt;p&gt;The whole harness is this. &lt;code&gt;CORPUS&lt;/code&gt; is a dict of tasks, each holding one buggy state, five valid candidates, five invalid ones, and six suites; every candidate defines &lt;code&gt;f&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;candidate&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;passes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suite_src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;impl&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suite_src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;suite&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;impl&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="c1"&gt;# per (task, suite)
&lt;/span&gt;&lt;span class="n"&gt;fails_buggy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;passes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buggy&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;accepted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;passes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="n"&gt;rejected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;invalid&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;passes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fails_buggy&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;passes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="n"&gt;jsf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fails_buggy&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accepted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rejected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six tasks, six suites each: 36 measured pairs. Every task is a small pure function whose spec leaves one dimension open — rounding, case, punctuation, container type, key order, transliteration — and the valid candidates differ only on those open dimensions. One of them, &lt;code&gt;whole_minutes&lt;/code&gt;, is spec'd as "return the number of whole minutes in a duration given in seconds"; &lt;code&gt;seconds // 60&lt;/code&gt; and &lt;code&gt;round(seconds / 60)&lt;/code&gt; are both legal readings of it.&lt;/p&gt;

&lt;p&gt;I wrote the candidates first, choosing what seemed natural for the spec, and only then wrote the suites the way a suite gets written when its author has the reference open: by asserting what that implementation does on edge inputs. No candidate was edited afterwards to break a suite. That ordering is the only thing standing between this and a rigged result, and you should hold it against me if the numbers look too clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reading&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;single-reference success, all pairs&lt;/td&gt;
&lt;td&gt;20/36 = 55.56%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;joint success, all pairs&lt;/td&gt;
&lt;td&gt;1/36 = 2.78%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;single-reference, pairs that fail the buggy state&lt;/td&gt;
&lt;td&gt;20/22 = 90.91%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;joint success, same pairs&lt;/td&gt;
&lt;td&gt;1/22 = 4.55%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The direction matches the paper and does not match its magnitude. My valid candidates differ more aggressively than theirs, so joint success collapses further. Treat the ratio as not reproduced; treat the direction as reproduced.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I did not expect: two failure modes, indistinguishable
&lt;/h2&gt;

&lt;p&gt;Of the 20 suites that pass the reference implementation, one also passes jointly. The other 19 split like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;How the suite failed&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;rejected at least one valid candidate&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;accepted at least one invalid candidate&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;both&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Eight suites were over-fitted to the reference: &lt;code&gt;palindrome/case_sensitive&lt;/code&gt; asserts &lt;code&gt;f("Aba") is False&lt;/code&gt; because the reference is case-sensitive, and three of my valid candidates lowercase first. Six suites were too weak: the same suite accepted four of five planted defects. Five did both at once.&lt;/p&gt;

&lt;p&gt;A single joint-success number folds all nineteen into "failed". If you are using that number to drive an agent's repair loop — generate a suite, score it, tell the model to try again — the score tells the model that something is wrong and nothing about what. Over-specification wants the assertion loosened; under-assertion wants it strengthened. Those are opposite edits, and the metric cannot distinguish the states that call for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  A third of my suites never failed the buggy state
&lt;/h2&gt;

&lt;p&gt;Fourteen of the 36 suites passed the buggy initial state, which sets &lt;code&gt;r_i(b_i) = 0&lt;/code&gt; and scores them zero under both metrics. That is correct per the definition, and it hides something: &lt;code&gt;percentile/median_odd&lt;/code&gt; asserts &lt;code&gt;f([1, 2, 3, 4, 5], 50) == 3&lt;/code&gt;, and my buggy implementation — which forgets to sort — happens to return 3 for that input. The suite is not over-specified. It is a test that does not test.&lt;/p&gt;

&lt;p&gt;So a zero under joint success means at least three different things: the suite rejects a legal implementation, the suite accepts a broken one, or the suite cannot see the bug at all. Thirds of my corpus, roughly: 14, 8+5, 6+5. Three repairs, one score.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type assertions and empty-input assertions are not tests
&lt;/h2&gt;

&lt;p&gt;The two most contentless shapes I found, both of which pass a reference implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;assert isinstance(f(120), int)&lt;/code&gt; accepted three of five defects, including one that returns hours instead of minutes and one that is off by one.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assert f([], 2) == []&lt;/code&gt; accepted four of five, including a chunker that drops the remainder and one that pads the last chunk with &lt;code&gt;None&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither assertion can fail for a wrong answer on any non-empty input, so neither constrains the behaviour the task is about. If you are writing or generating tests, these are the two lines to delete first. They are also the two lines most likely to survive an agent's self-review, because they pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number depends on the panel
&lt;/h2&gt;

&lt;p&gt;The paper's own analysis notes that joint success depends on which implementations the panel contains, and studies growing panels. My corpus is small enough to enumerate every panel, so here is the same suites scored on every sub-panel that can be drawn from five valid and five invalid candidates:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Panel&lt;/th&gt;
&lt;th&gt;Evaluations&lt;/th&gt;
&lt;th&gt;Mean joint success&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 valid + 1 invalid&lt;/td&gt;
&lt;td&gt;900&lt;/td&gt;
&lt;td&gt;35.44%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 + 2&lt;/td&gt;
&lt;td&gt;3,600&lt;/td&gt;
&lt;td&gt;23.22%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3 + 3&lt;/td&gt;
&lt;td&gt;3,600&lt;/td&gt;
&lt;td&gt;14.61%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4 + 4&lt;/td&gt;
&lt;td&gt;900&lt;/td&gt;
&lt;td&gt;8.00%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 + 5&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;2.78%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same suites, same implementations, a factor of 12.7 between the smallest and largest panel. A joint-success figure quoted without its panel size and composition is not portable between corpora. That is not a flaw in the paper — it reports one panel and studies growth on it — but it does mean the headline ratio cannot be compared across papers unless the panel is held fixed, and here the panel is what moves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would change in an agent loop
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Score the two directions separately. Rejecting a valid candidate and accepting an invalid one are different defects; report &lt;code&gt;valid acceptance rate&lt;/code&gt; and &lt;code&gt;invalid rejection rate&lt;/code&gt; alongside the joint number, as the paper also does, and let the repair prompt see which one moved.&lt;/li&gt;
&lt;li&gt;Keep a few candidates that differ only in format and type. If a suite fails those, it is over-fitted and the fix is to loosen it — cheap to detect, and impossible to see from a single number.&lt;/li&gt;
&lt;li&gt;Never let a type-only or empty-input assertion count as coverage. Both passed a majority of planted defects here.&lt;/li&gt;
&lt;li&gt;Report the panel. If the number moves 12.7x with panel size on six hand-built tasks, it moves on three hundred real ones too.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;Six tasks, 36 suites, all authored by me, on specs I wrote to be deliberately under-determined — which makes this the hard case, not the average case. I authored both the corpus and the suites, and the only defence against a rigged result is the authoring order stated above plus the corpus being short enough to read. The single-reference figure of 90.91% on eligible pairs is not comparable to the paper's 59.67%: different corpus, different tasks, and my valid candidates are more diverse than theirs by construction. Nothing here disputes their finding; it adds the decomposition and the measured panel sensitivity.&lt;/p&gt;

&lt;p&gt;If you want to run it yourself, the harness above plus one task's five valid and five invalid candidates is enough to reproduce a single row of the first table, and that is where I would start. The interesting number is not the joint success rate — it is how often a suite passes the reference while being unable to fail.&lt;/p&gt;




&lt;p&gt;I build EMRG, a self-evolving coding agent, at &lt;a href="https://github.com/argszero/emrg" rel="noopener noreferrer"&gt;github.com/argszero/emrg&lt;/a&gt;; the reason this measurement exists is that its gates were passing when they should have failed, which is the same defect one layer up.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
