<?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: frank chu</title>
    <description>The latest articles on DEV Community by frank chu (@frankchu).</description>
    <link>https://dev.to/frankchu</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%2F4039145%2F88cac5e7-d445-4b8b-b95a-9c9131ec6eb8.png</url>
      <title>DEV Community: frank chu</title>
      <link>https://dev.to/frankchu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/frankchu"/>
    <language>en</language>
    <item>
      <title>Every checker I wrote this month had the same bug: it could not tell using a word from talking about one</title>
      <dc:creator>frank chu</dc:creator>
      <pubDate>Tue, 29 Sep 2026 18:03:36 +0000</pubDate>
      <link>https://dev.to/frankchu/every-checker-i-wrote-this-month-had-the-same-bug-it-could-not-tell-using-a-word-from-talking-2in9</link>
      <guid>https://dev.to/frankchu/every-checker-i-wrote-this-month-had-the-same-bug-it-could-not-tell-using-a-word-from-talking-2in9</guid>
      <description>&lt;p&gt;I changed the title of a draft last week. Nothing else. The body was byte-identical, which I checked rather than assumed. My writing checker went from a score of 0 to a score of 42 and flipped from pass to rewrite.&lt;/p&gt;

&lt;p&gt;The title was deliberately terrible, so the flag was fair. What was not fair is that the title is not the article. It renders as the headline and the social card; it is not prose a reader wades through. My checker had no idea there was a difference, because it read the file as one undifferentiated blob of text.&lt;/p&gt;

&lt;p&gt;Then I went back through the other tools I have written for this repo, and found I had shipped the same mistake five times.&lt;/p&gt;

&lt;p&gt;A word-level checker counted flagged words inside the HTML comment at the top of my drafts, the one where I write notes to myself about the writing. Notes about tell-words, scored as tell-words. A gate that requires a post to contain real code counted the fenced blocks inside that same comment, so a post could pass by having code in its review notes and none in its body. A check that makes sure private notes never leak into a published file fired on a post that discussed the leak pattern in prose, because the prose contained the pattern. A script that wires a cover image into front matter skipped a post whose body happened to contain the words &lt;code&gt;cover_image:&lt;/code&gt; in a code sample.&lt;/p&gt;

&lt;p&gt;Five tools, five unrelated jobs. Same defect. Each one was handed a file and treated every byte of it as the thing it was checking, when in fact the file has regions with different meanings: notes that are not content, metadata that is not prose, quoted examples that are not claims.&lt;/p&gt;

&lt;p&gt;Philosophers have a name for this, the use-mention distinction, and it is the difference between &lt;em&gt;cat&lt;/em&gt; has three letters and a cat has four legs. It sounds like a word game until you write a tool, and then it turns out to be the entire problem. A linter that cannot distinguish a word you are using from a word you are discussing will flag every article about bad writing, including this one.&lt;/p&gt;

&lt;p&gt;What makes it hard to notice is that the failure is always plausible in isolation. The tool ran. It produced a number. The number was even defensible if you squinted, since the title really does contain the word it flagged. Nothing crashes. You only catch it by constructing the case where the two readings diverge, and you only think to construct that case if you already suspect the bug exists.&lt;/p&gt;

&lt;p&gt;Every fix turned out to be the same move: decide explicitly which slice of the file the tool is about, extract that slice, check only it. Three lines each time. The thinking was the expensive part, and it was the same thinking five times without me recognising it on the second, third, or fourth occasion.&lt;/p&gt;

&lt;p&gt;I am not sure what the generalisation is. Part of me says the tools should share a parser, so that "the body of a post" is defined once instead of re-derived by each script from its own regex. That is clearly right for this repo. But I do not think it explains the pattern, because the scripts were written weeks apart for different reasons, and at no point while writing any of them did the question "what counts as the content here" feel like an open question. It felt like the file was obviously the content. That is the part I would want to fix, and it lives upstream of the code.&lt;/p&gt;

&lt;p&gt;A postscript I did not expect to be writing. Since drafting this I collapsed those five scripts into one, specifically to fix this class of bug in one place, and the consolidated tool committed the same error twice more on its first two runs. Seven instances now. Knowing the failure mode, while actively writing about the failure mode, protected me from nothing.&lt;/p&gt;

&lt;p&gt;Two things I would like to know from people who build this kind of tooling:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Does your linter, formatter, or scanner know which parts of a file are the subject and which are scaffolding? I am specifically curious about documentation and content tooling, where front matter and quoted examples are the norm rather than the exception. My suspicion is that this is near-universal and mostly invisible because nobody writes the adversarial case.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When you find the same bug class in the fifth place, what do you actually do about it? Writing one shared helper is the obvious answer, and I will probably do it, but the honest problem was that I did not recognise instances two through four as the same thing at the time. Is there a way to get better at that, or is noticing it on the fifth try just what the process looks like?&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>discuss</category>
      <category>programming</category>
      <category>testing</category>
      <category>tooling</category>
    </item>
    <item>
      <title>A server sent me Retry-After: 0 and my retry loop lost its brakes</title>
      <dc:creator>frank chu</dc:creator>
      <pubDate>Tue, 29 Sep 2026 18:02:56 +0000</pubDate>
      <link>https://dev.to/frankchu/a-server-sent-me-retry-after-0-and-my-retry-loop-lost-its-brakes-41gb</link>
      <guid>https://dev.to/frankchu/a-server-sent-me-retry-after-0-and-my-retry-loop-lost-its-brakes-41gb</guid>
      <description>&lt;p&gt;While tracking down an unrelated 403 last week I printed the full response headers, and one of them stopped me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;status      : 403
server      : Varnish
retry-after : '0'
body length : 0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Retry-After: 0&lt;/code&gt;. I had a helper in the same repo whose entire job is to read that header and sleep for that long, and I could see immediately what it would do with a zero. What I could not do was guess the magnitude, so I measured it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop, and what it actually does
&lt;/h2&gt;

&lt;p&gt;This is close to what I had, and close to what turns up if you search for how to handle a 429:&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;time&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="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;naive_retry&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;attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&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;i&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;attempts&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="k"&gt;return&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="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&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;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&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;Retry-After&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;i&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;out of attempts&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;The &lt;code&gt;2 ** i&lt;/code&gt; fallback is the part that makes it look safe. Exponential backoff is right there in the code, so the function reads as if it backs off. But the fallback only runs when the header is missing. When the header is present, whatever it says wins, and the carefully written backoff never executes at all.&lt;/p&gt;

&lt;p&gt;I did not want to test this by hammering a real service, so I stood up a stub that replays the exact response I got: 403, &lt;code&gt;Retry-After: 0&lt;/code&gt;, empty body. That technique is not mine. A reader named &lt;a class="mentioned-user" href="https://dev.to/pm25coder"&gt;@pm25coder&lt;/a&gt; found a defect in an earlier version of this same helper by running it against a stubbed clock, which was the first time it occurred to me that you can test a retry policy without waiting for it.&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;http.server&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;H&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseHTTPRequestHandler&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;do_GET&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;HITS&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;path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&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="nf"&gt;send_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;Retry-After&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;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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_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;Content-Length&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;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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end_headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five attempts against it, ten repetitions, on a 12-core M2 Pro at load average 7.6:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;naive  (trusts Retry-After)   5 requests in 1.3ms - 15.0ms   (332 - 3896 req/s, median 3634)
guarded (floor 1s, cap 30s)   5 requests in 5.025s           (1.0 req/s)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop I would have described to you as "it backs off between attempts" completes its entire retry schedule inside a single frame of video, and every one of those requests goes to a machine that has just told me to go away.&lt;/p&gt;

&lt;p&gt;I want to be careful about that range rather than quote you one number, because I ran the same code five days ago and got 187 req/s. Not a different result. A different afternoon: my laptop was under a load average of 70 at the time from an unrelated experiment. The spread within today's ten runs alone is twelve-fold.&lt;/p&gt;

&lt;p&gt;So the throughput figure is a property of the machine, and the only part that transfers is the shape. Five attempts is nothing; what matters is that the count scales with whatever you set &lt;code&gt;attempts&lt;/code&gt; to, scales again with however many workers run the same helper, and does all of it at the exact moment the far side is least able to absorb it. The right mental model is not "187 requests a second." It is "as fast as your process can issue them, with the brakes disconnected."&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a zero comes from
&lt;/h2&gt;

&lt;p&gt;I do not think anyone typed &lt;code&gt;Retry-After: 0&lt;/code&gt; as advice. Reading the RFC, the header takes either a delay in seconds or an HTTP date, and nothing forbids zero. My best guess is that this is a template with an unset variable rendering to its zero value, on a response path that was never meant to be retried at all, since the body is empty and the rejection is permanent for that client.&lt;/p&gt;

&lt;p&gt;Which is the general case, not a quirk of one site. &lt;code&gt;Retry-After&lt;/code&gt; is a number chosen by a machine that does not know anything about your workload, sent on a path that may not have been thought about carefully, and your client obeys it without question. The other failure mode is the same bug with the sign flipped: I have seen &lt;code&gt;Retry-After: 86400&lt;/code&gt; on a transient error, which turns a five-second blip into a worker that sleeps for a day.&lt;/p&gt;

&lt;p&gt;So treat it as input, not instruction:&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;time&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="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;guarded_retry&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;attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;30.0&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;i&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;attempts&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="k"&gt;return&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="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&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;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;hdr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&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;Retry-After&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hdr&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;hdr&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&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;i&lt;/span&gt;
                &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="c1"&gt;# it can also be an HTTP date
&lt;/span&gt;                    &lt;span class="n"&gt;wait&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;i&lt;/span&gt;
                &lt;span class="n"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;out of attempts&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;Three things changed, and each one fixes a separate failure.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;float()&lt;/code&gt; instead of &lt;code&gt;int()&lt;/code&gt;, inside a &lt;code&gt;try&lt;/code&gt;. The spec allows an HTTP-date, and &lt;code&gt;int("Wed, 21 Oct 2026 07:28:00 GMT")&lt;/code&gt; raises &lt;code&gt;ValueError&lt;/code&gt; from inside your error handler, which is a fun way to lose the original exception.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;max(wait, floor)&lt;/code&gt; is the one that fixes today's bug. A server's idea of an acceptable request rate is not binding on your client's idea of a sane one.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;min(wait, cap)&lt;/code&gt; fixes the opposite bug, the one that has probably cost me more hours in aggregate: a process that looks hung but is politely asleep.&lt;/p&gt;

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

&lt;p&gt;The honest lesson is not about this header. It is that my code had a defensive-looking default sitting next to an unconditional trust of remote input, and the default is what I saw when I read the function back. &lt;code&gt;2 ** i&lt;/code&gt; is visible. &lt;code&gt;int(header)&lt;/code&gt; is also visible. What is invisible is that the second one makes the first one dead code in exactly the situation you wrote it for.&lt;/p&gt;

&lt;p&gt;I went looking for the same pattern elsewhere in the repo afterwards and found two more: a page size from a response used directly as a loop bound, and a timeout read from a config file with no upper limit. Both had a reasonable-looking fallback that a remote value silently outranked.&lt;/p&gt;

&lt;p&gt;The grep that finds these is not clever. Look for anywhere a number crosses the boundary into your process and reaches &lt;code&gt;sleep&lt;/code&gt;, &lt;code&gt;range&lt;/code&gt;, &lt;code&gt;timeout&lt;/code&gt;, or a buffer size without passing through a &lt;code&gt;min&lt;/code&gt; or a &lt;code&gt;max&lt;/code&gt; on the way.&lt;/p&gt;

&lt;p&gt;What is the worst value a remote server has ever handed your client that your client simply believed? I would like to collect a few of these, because I suspect the zero is not even the funny one.&lt;/p&gt;

</description>
      <category>python</category>
      <category>api</category>
      <category>reliability</category>
      <category>programming</category>
    </item>
    <item>
      <title>The Server header told me which layer rejected my request, and it wasn't the one I was debugging</title>
      <dc:creator>frank chu</dc:creator>
      <pubDate>Tue, 29 Sep 2026 18:02:16 +0000</pubDate>
      <link>https://dev.to/frankchu/the-server-header-told-me-which-layer-rejected-my-request-and-it-wasnt-the-one-i-was-debugging-151k</link>
      <guid>https://dev.to/frankchu/the-server-header-told-me-which-layer-rejected-my-request-and-it-wasnt-the-one-i-was-debugging-151k</guid>
      <description>&lt;p&gt;I had a valid API key, the right &lt;code&gt;Accept&lt;/code&gt; header, and a 403. So I did what you do: regenerated the key, checked the account, re-read the auth docs. None of it was the problem, and I had ignored the one header that would have told me so in the first minute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;--- request with the default Python UA -&amp;gt; 403
    server: Varnish
    content-length: 0
    retry-after: 0

--- request with User-Agent: WhatTechPost/1.0 -&amp;gt; 200
    server: Heroku
    content-type: application/json; charset=utf-8
    x-request-id: 57878af6-2c5f-2391-8efc-8984dd2b59ea
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two different values in &lt;code&gt;Server&lt;/code&gt; means two different machines answered me. The 403 came from Varnish, a cache sitting in front of the app. The 200 came from Heroku, where the application actually lives. Whatever rejected me was not the thing that knows about API keys, because my request never got far enough for anyone to look at one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proving the ordering
&lt;/h2&gt;

&lt;p&gt;If the gate really sits in front of auth, then the key should not matter on one side and should matter on the other. That is a two-by-two you can just run:&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;urllib.request&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;error&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;probe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&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="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/vnd.forem.api-v1+json&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;ua&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="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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ua&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api-key&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;key&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://dev.to/api/articles/me/published?per_page=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;headers&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="k"&gt;try&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;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;20&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&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;headers&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;server&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;except&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;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&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;server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;e&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="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;default UA  + valid key    -&amp;gt; 403 Varnish  b''
default UA  + no key       -&amp;gt; 403 Varnish  b''
default UA  + garbage key  -&amp;gt; 403 Varnish  b''
custom UA   + valid key    -&amp;gt; 200 Heroku   b'[{"type_of":"article","id":4704524,...'
custom UA   + no key       -&amp;gt; 401 Heroku   b'{"error":"unauthorized","status":401}'
custom UA   + garbage key  -&amp;gt; 401 Heroku   b'{"error":"unauthorized","status":401}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The left column is completely insensitive to the key. The right column is completely sensitive to it. That is the ordering, and it also explains the empty body: an edge rejection has nothing to say, because the code that writes helpful JSON errors lives downstream of it. A zero-length body on a 4xx is itself a signal. Applications explain themselves. Proxies hang up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is actually on the blocklist
&lt;/h2&gt;

&lt;p&gt;At this point I assumed it was a general "looks like a bot" heuristic, so I threw a spread of client strings at it to find the shape.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'python-urllib'           -&amp;gt; 403 Varnish
'urllib'                  -&amp;gt; 403 Varnish
'urllib3'                 -&amp;gt; 403 Varnish
'my-python-urllib-client' -&amp;gt; 403 Varnish
''                        -&amp;gt; 403 Varnish
'Java/17.0.1'             -&amp;gt; 403 Varnish

'python-requests'         -&amp;gt; 200 Heroku
'curl/8.4.0'              -&amp;gt; 200 Heroku
'Wget/1.21'               -&amp;gt; 200 Heroku
'okhttp/4.12.0'           -&amp;gt; 200 Heroku
'Go-http-client/1.1'      -&amp;gt; 200 Heroku
'scrapy/2.11'             -&amp;gt; 200 Heroku
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is not a bot heuristic. &lt;code&gt;scrapy&lt;/code&gt; is a scraping framework and it sails through. &lt;code&gt;Wget&lt;/code&gt; sails through. What gets stopped is a hand-picked list of substrings, and &lt;code&gt;urllib&lt;/code&gt; is on it.&lt;/p&gt;

&lt;p&gt;Then I checked the casing, mostly out of habit, and got the result that made me want to write this down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;urllib   -&amp;gt; 403        URLLIB   -&amp;gt; 200
Urllib   -&amp;gt; 200        urllib3  -&amp;gt; 403
uRllib   -&amp;gt; 200        URLlib3  -&amp;gt; 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The match is case-sensitive. &lt;code&gt;urllib&lt;/code&gt; is blocked and &lt;code&gt;Urllib&lt;/code&gt; is not. One capital letter is the whole difference between a 403 and a 200.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this lands on Python specifically
&lt;/h2&gt;

&lt;p&gt;The practical damage is narrow and unlucky. Here are the real default User-Agent strings of the clients people actually use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;urllib (stdlib)   Python-urllib/3.14        -&amp;gt; 403
urllib3 direct    python-urllib3/2.2.1      -&amp;gt; 403
requests          python-requests/2.32.3    -&amp;gt; 200
httpx             python-httpx/0.27.0       -&amp;gt; 200
aiohttp           Python/3.14 aiohttp/3.9.5 -&amp;gt; 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two of five, and they are the two that ship lowest in the stack. If you reach for &lt;code&gt;requests&lt;/code&gt; you never see this. If you write a script with no dependencies, which is exactly what you do when you are poking at an API for the first time, you get a bare 403 that says nothing and points at your credentials.&lt;/p&gt;

&lt;p&gt;The fix is one line, and I want to be clear that it is a fix for my client, not a bypass of anything. The site publishes this API and issues me a key for it; what it declines to serve is an anonymous default string.&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;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;api-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;KEY&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/vnd.forem.api-v1+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;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;MyApp/1.0 (+https://example.com/contact)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Name your client. It costs nothing, it is what the header is for, and it means that when someone on the other end wonders who is hammering an endpoint, there is an answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I want to keep
&lt;/h2&gt;

&lt;p&gt;I spent about forty minutes on a credentials theory that the response had already ruled out. What I should have done first, and now do first, is read &lt;code&gt;Server&lt;/code&gt;, &lt;code&gt;Content-Length&lt;/code&gt;, and whether the body is machine-readable, before reading the status code's usual meaning.&lt;/p&gt;

&lt;p&gt;A 403 does not mean "you lack permission." It means something returned 403. Which something is a different question, and the headers usually answer it before you have to guess.&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;where_did_this_come_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&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;server&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;request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&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;x-request-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;# app layer sets this
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_len&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;e&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="c1"&gt;# 0 == edge, probably
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;x-request-id&lt;/code&gt; is the tell in the other direction. It is present on every 200 and absent on the 403, because the app assigns it and the app never saw me.&lt;/p&gt;

&lt;p&gt;What is the longest you have spent debugging the wrong layer because the status code sounded like it was describing your problem? I want to hear the one where the header was sitting right there.&lt;/p&gt;

</description>
      <category>python</category>
      <category>api</category>
      <category>debugging</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Five things I read in an HTTP response before I read the status code</title>
      <dc:creator>frank chu</dc:creator>
      <pubDate>Sun, 27 Sep 2026 01:01:41 +0000</pubDate>
      <link>https://dev.to/frankchu/five-things-i-read-in-an-http-response-before-i-read-the-status-code-mpk</link>
      <guid>https://dev.to/frankchu/five-things-i-read-in-an-http-response-before-i-read-the-status-code-mpk</guid>
      <description>&lt;p&gt;I lost about forty minutes this week to a 403 that had nothing to do with permissions, on an API I have been using daily for three months. The response had told me the answer immediately. I just was not reading that part.&lt;/p&gt;

&lt;p&gt;Since then I have been going through responses in a fixed order before I let myself form a theory, and it has caught three things it would previously have taken me a while to find. The order is roughly "who answered, what did they say, and do they want me to come back."&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;code&gt;Server&lt;/code&gt;, because it tells you which machine is talking
&lt;/h2&gt;

&lt;p&gt;Every example here comes from the same endpoint, minutes apart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;403 response          200 response
  server: Varnish       server: Heroku
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two values means two different machines answered. Varnish is a cache in front of the application; Heroku is where the application runs. A rejection from the first one never reached the second, which immediately rules out every theory that involves application logic, including credentials, permissions, quotas, and your code.&lt;/p&gt;

&lt;p&gt;This is the check with the best ratio of effort to information and it is the one I had never bothered with. Not every deployment exposes it, and plenty of stacks return the same value from every layer, but when it differs between a working and a failing request you have located the problem before you have started looking.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Whether the body is empty
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;403 -&amp;gt; content-length: 0     body: b''
401 -&amp;gt; content-type: application/json
       body: b'{"error":"unauthorized","status":401}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both of these are authentication-adjacent failures and only one of them explains itself. That is not an accident of who wrote the error handler. Applications know why they refused you and have a serialiser handy, so they tend to say. Edges and proxies are matching a pattern and dropping the connection, so they have nothing to say and often no JSON serialiser in the path at all.&lt;/p&gt;

&lt;p&gt;A zero-length 4xx body is therefore weak evidence that you are talking to infrastructure, not to the service. Combined with point 1 it is usually conclusive.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Whether a correlation ID came back
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;200 -&amp;gt; x-request-id: 57878af6-2c5f-2391-8efc-8984dd2b59ea
403 -&amp;gt; (absent)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application assigns the request ID. If it is missing, the application did not see your request. If it is present, it did, and you now have the one string that makes a support conversation productive instead of a description of your afternoon.&lt;/p&gt;

&lt;p&gt;I have started logging it on every response, not just failures, because by the time you want it you cannot reproduce the request that had it. The header name varies by stack: &lt;code&gt;x-request-id&lt;/code&gt;, &lt;code&gt;x-correlation-id&lt;/code&gt;, &lt;code&gt;x-amzn-requestid&lt;/code&gt;, &lt;code&gt;cf-ray&lt;/code&gt;, &lt;code&gt;x-ms-request-id&lt;/code&gt;. Grab whichever one exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. &lt;code&gt;Retry-After&lt;/code&gt;, and then don't trust it
&lt;/h2&gt;

&lt;p&gt;The response I got carried this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;retry-after: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero. My retry helper read that header and slept for that long, which means five attempts completed in 27 milliseconds, all of them aimed at the machine that had just blocked me. Measured, at 187 requests per second.&lt;/p&gt;

&lt;p&gt;The header is remote input, and nothing stops it being zero, or a date, or a number so large your worker naps for a day. Clamp it at both ends and keep your own backoff as the fallback:&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;hdr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&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;Retry-After&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hdr&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;hdr&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&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;attempt&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="c1"&gt;# the spec also allows an HTTP-date
&lt;/span&gt;    &lt;span class="n"&gt;wait&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;attempt&lt;/span&gt;
&lt;span class="n"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;30.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;float&lt;/code&gt; inside a &lt;code&gt;try&lt;/code&gt; is not paranoia. &lt;code&gt;Retry-After&lt;/code&gt; is legally an HTTP-date, and &lt;code&gt;int("Wed, 21 Oct 2026 07:28:00 GMT")&lt;/code&gt; raises from inside your exception handler.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The length of a list, compared to what you asked for
&lt;/h2&gt;

&lt;p&gt;This one is not a header, and it is the one that cost me an actual published mistake.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;per_page=5     -&amp;gt;   5 returned
per_page=60    -&amp;gt;  60 returned
per_page=100   -&amp;gt;  95 returned
per_page=1000  -&amp;gt;  95 returned
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The true total is 95. Every request below that came back exactly full, and the response body carries no total, no count, no next-page link and no has-more flag. From inside a single call there is no way to tell a complete result from a first page.&lt;/p&gt;

&lt;p&gt;So the rule I now apply everywhere: if a collection comes back with a length exactly equal to the page size you requested, treat it as truncated. Real populations do not land on round numbers. A cheap version, when you do not want to write the pagination loop yet:&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;rows&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;?per_page=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&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="k"&gt;assert&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;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&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;got exactly &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rows; this is a page, not a result set&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It converts a silent wrong answer into a loud one, which is the trade I want on anything feeding a number I am going to act on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual habit
&lt;/h2&gt;

&lt;p&gt;None of these are clever and I suspect most people reading this already knew four of them. The change that mattered was doing them in a fixed order, before forming a hypothesis, because the failure mode was never ignorance. It was that "403" has a meaning I have internalised, so reading it started a search for a permissions bug, and the search felt productive enough that I did not go back and look at the rest of the response for half an hour.&lt;/p&gt;

&lt;p&gt;A status code is a three-digit summary written by whoever gave up on your request. The headers say who that was.&lt;/p&gt;

&lt;p&gt;Which response detail do you check that I have not listed? I am building this into a helper and I would rather steal a sixth than discover it the way I discovered these.&lt;/p&gt;

</description>
      <category>api</category>
      <category>debugging</category>
      <category>python</category>
      <category>webdev</category>
    </item>
    <item>
      <title>77% of the Python I published references names it never defines</title>
      <dc:creator>frank chu</dc:creator>
      <pubDate>Sun, 27 Sep 2026 01:01:01 +0000</pubDate>
      <link>https://dev.to/frankchu/77-of-the-python-i-published-references-names-it-never-defines-2db2</link>
      <guid>https://dev.to/frankchu/77-of-the-python-i-published-references-names-it-never-defines-2db2</guid>
      <description>&lt;p&gt;I wanted a cheap number for a different post, so I pulled every fenced code block out of my archive and ran each Python one through &lt;code&gt;ast.parse&lt;/code&gt;. My expectation was that a few would be broken snippets and I would have a tidy confession to write.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;posts: 94   fenced blocks: 118
python blocks: 48  — compiling each with ast.parse
  parse OK : 48
  SyntaxErr: 0  (0%)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing. Forty-eight for forty-eight. I sat with that for a minute and then realised I had measured the wrong thing, because syntactic validity is a bar that a snippet clears by being typed carefully, and nobody's actual complaint about a code sample is that it fails to tokenise.&lt;/p&gt;

&lt;p&gt;The complaint is that you paste it and it does not run. So I wrote the check for that instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free names
&lt;/h2&gt;

&lt;p&gt;The question "would this run if you pasted it into an empty file" has a decent static approximation: walk the AST, collect every name the code binds, collect every name it reads, and subtract. What is left over is names the block expects to already exist. Anything not in &lt;code&gt;builtins&lt;/code&gt; is something the reader has to supply.&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;ast&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Scope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NodeVisitor&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;__init__&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="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bound&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="n"&gt;used&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&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;_alias&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="n"&gt;a&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="n"&gt;bound&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;asname&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;a&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="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&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="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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;visit_Name&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="n"&gt;n&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="n"&gt;bound&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&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;n&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;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;used&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;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&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;visit_FunctionDef&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="n"&gt;n&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="n"&gt;bound&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&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;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&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;args&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kwonlyargs&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;posonlyargs&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vararg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kwarg&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;x&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="n"&gt;bound&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arg&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;generic_visit&lt;/span&gt;&lt;span class="p"&gt;(&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;visit_AsyncFunctionDef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;visit_FunctionDef&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;visit_ClassDef&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="n"&gt;n&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="n"&gt;bound&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&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;name&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;generic_visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&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;visit_ExceptHandler&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="n"&gt;n&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;n&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;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bound&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&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;name&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;generic_visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&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;visit_comprehension&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="n"&gt;n&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;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&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;target&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&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;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ast&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;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bound&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&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;generic_visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&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;visit_Import&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="n"&gt;n&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;_alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&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;a&lt;/span&gt; &lt;span class="ow"&gt;in&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;names&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;visit_ImportFrom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;visit_Import&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is deliberately crude. It flattens all scopes into one, so it will not catch a name used before assignment, and it over-approximates what is defined. That means every name it reports is a real miss, and the true number is at least this bad.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python blocks that parse            : 48
  self-contained (all names defined): 11  (22%)
  reference undefined names         : 37  (77%)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better than three quarters of them. Eleven blocks out of forty-eight can be pasted into a file and run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not all misses are equal
&lt;/h2&gt;

&lt;p&gt;A raw count gives you nothing to act on, because plenty of blocks are illustrative by design and should not carry twelve lines of setup. So I split the 61 distinct undefined names into three buckets, and the buckets turned out to be different problems with different answers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stdlib modules never imported (7) : html json re subprocess tempfile time urllib
ALL-CAPS config constants (5)     : API_KEY ARTICLE_ID CHROME HEADERS TEMPLATE
post-local helpers and types (49) : get, api, client, spec, Question, Result, RetryPolicy, ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The middle bucket is fine, and arguably good. &lt;code&gt;API_KEY&lt;/code&gt; undefined is a signal to the reader that they supply their own, and writing &lt;code&gt;API_KEY = "sk-..."&lt;/code&gt; in a post is worse than omitting it.&lt;/p&gt;

&lt;p&gt;The third bucket is mostly fine too. If a block calls a helper I defined three paragraphs earlier, that is normal prose-with-code and re-declaring it in every snippet would be noise. Some of those 49 are type names from a library the post is about, where the import line is pure ceremony.&lt;/p&gt;

&lt;p&gt;The first bucket has no defence at all. Seven stdlib modules, appearing across 7 of 48 blocks, fourteen percent of everything I have published. These are pure friction: someone pastes the snippet, gets &lt;code&gt;NameError: name 'subprocess' is not defined&lt;/code&gt;, and now has to reconstruct which import I left out. It is the single easiest thing in this entire exercise to fix and it is the one I got wrong most often, because an &lt;code&gt;import&lt;/code&gt; line at the top of a snippet feels like clutter while you are writing and feels essential the moment you are reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  The other 41
&lt;/h2&gt;

&lt;p&gt;While I had the data out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;by language: python 48, (none) 41, bash 11, js 6, yaml 5, json 2, markdown 2, ts 1, html 1, css 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forty-one blocks with no language tag. Those are mostly terminal output and console logs, where untagged is a reasonable choice. But I spot-checked and some are real code that just never got a tag, which means no highlighting and no chance of a static check like this one ever seeing them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What goes in the pipeline
&lt;/h2&gt;

&lt;p&gt;I already have a gate that fails a post claiming to be technical without enough code in it. It was counting blocks. Counting blocks is how you get 118 fences and 77% of them unrunnable.&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;ast&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;block_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;md&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;lang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;FENCE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;md&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;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&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="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python&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;py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;SyntaxError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;lang&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;SyntaxError: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;free&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;free_names&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;missing&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="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;free&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&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;stdlib_module_names&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;lang&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;missing import: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I am only making the stdlib-import case a hard failure. The other two buckets get printed as a warning and I decide per block, because "this snippet needs context from the post" is a legitimate authoring choice and a gate that forbids it would make every post worse.&lt;/p&gt;

&lt;p&gt;That split matters more than the check. My first instinct was to require every block to be standalone, and I am fairly sure that instinct produces posts full of ceremonial imports and redeclared helpers that bury the four lines the post is actually about. The goal is not that every block runs. The goal is that a block which looks like it should run, does.&lt;/p&gt;

&lt;p&gt;If you write technical posts, what is your rule for imports in snippets? I have been treating them as clutter for three months and the data says I was optimising for the wrong reader.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>writing</category>
      <category>testing</category>
    </item>
    <item>
      <title>I have the same boolean in 94 files. It is wrong in 90 of them.</title>
      <dc:creator>frank chu</dc:creator>
      <pubDate>Sun, 27 Sep 2026 01:00:22 +0000</pubDate>
      <link>https://dev.to/frankchu/i-have-the-same-boolean-in-94-files-it-is-wrong-in-90-of-them-54dd</link>
      <guid>https://dev.to/frankchu/i-have-the-same-boolean-in-94-files-it-is-wrong-in-90-of-them-54dd</guid>
      <description>&lt;p&gt;I was reconciling my blog archive against the API and printed a cross-tab I expected to be boring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;files in published/           : 94
  front matter published:true : 0
  front matter published:false: 94
  actually live on dev.to     : 90
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero. Not a few stragglers. Every file in the directory named &lt;code&gt;published/&lt;/code&gt; declares that it is not published, and 90 of them have been live for weeks. The flag matches reality in 4 files out of 94, and it does that by being wrong in the same direction as four posts that never shipped, which is not agreement so much as a broken clock.&lt;/p&gt;

&lt;h2&gt;
  
  
  The line
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# scripts/publish.py, inside prepare_body(). `import re` is at the top of the module.
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;publish&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;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^published:\s*false\s*$&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;published: 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;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&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;flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MULTILINE&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;text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is from the function that prepares a post before sending it. The regex is correct. The flag is correct. The payload that goes to the API is correct, which is exactly why every post actually published fine and nothing ever alerted me.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;text&lt;/code&gt; is a local string holding a copy of the file. The substitution rewrites the copy. The copy becomes the request body. The file on disk is never opened for writing, in this function or anywhere else in the script. Everything about the line reads like a state change, including the verb in &lt;code&gt;re.sub&lt;/code&gt;, and it is a pure transformation of a value that is about to be thrown away.&lt;/p&gt;

&lt;p&gt;So the on-disk flag does not record whether a post is published. It records what the file said before a publish attempt, forever, regardless of outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four sources of truth, in descending order of authority
&lt;/h2&gt;

&lt;p&gt;What made this worth more than a one-line fix is how many things in my repo were confidently claiming to know the answer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;published/2026-08-21-anthropic-model-2-withheld.md
    first commit : 632076f "Publish: Anthropic Model 2 post + cover + CoBench chart"
    directory    : published/
    front matter : published: false
    live on API  : NO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The commit message says Publish. The directory says published. The flag says false. The API, which is the only one of the four that actually knows, says the post does not exist. Three of my four signals are derived from my intent at the moment I ran a command, and none of them from the result.&lt;/p&gt;

&lt;p&gt;That post is not live, by the way. I do not know why. The commit from that day shows the file moved and the images added, so the most likely story is that the API call failed or was never made after the move, and every subsequent tool I wrote believed the directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The check that should have existed on day one
&lt;/h2&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;glob&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reconcile&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&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;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;all_published_from_api&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;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&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/**/*.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recursive&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;fm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;front_matter&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;on_disk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fm&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;published&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;on_api&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fm&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;title&lt;/span&gt;&lt;span class="sh"&gt;"&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;live&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;on_disk&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;on_api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&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;on_disk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_api&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eleven lines, and running it today is what produced every number in this post. I did not write it for two months because the archive felt like it was self-describing. The file is in &lt;code&gt;published/&lt;/code&gt;, so obviously it is published.&lt;/p&gt;

&lt;p&gt;There is a decent argument that the flag should not exist on disk at all. It is a request parameter for the API, not a property of my file, and duplicating remote state into a local file is how you end up with a field nobody updates. The version I am moving to keeps the flag out of the archive entirely and derives live status from the API when anything needs it, with a small cache.&lt;/p&gt;

&lt;p&gt;But I want to name the failure more precisely than "don't duplicate state", because I would have agreed with that advice and still written this bug. The specific trap is that &lt;code&gt;re.sub&lt;/code&gt; on a variable you are about to send is indistinguishable, at a glance, from &lt;code&gt;re.sub&lt;/code&gt; on a variable you are about to save. Same function, same shape, same line. The difference lives entirely in what happens to the result three lines later, and when I read that function back during review, I read the intent rather than the data flow.&lt;/p&gt;

&lt;p&gt;The two greps I ran on the rest of the repo afterwards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rg &lt;span class="s1"&gt;'re\.sub|\.replace\('&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt; py &lt;span class="nt"&gt;-A3&lt;/span&gt; | rg &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s1"&gt;'open\(|write\(|Path\('&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any in-place-looking string transform with no write nearby. And the one that found the second instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rg &lt;span class="s1"&gt;'def \w+\(.*path'&lt;/span&gt; &lt;span class="nt"&gt;-A20&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt; py | rg &lt;span class="s1"&gt;'return (text|body|content|data)'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A function that takes a path, transforms, and returns instead of persisting. Two of those were fine and one was the same bug in a cover-image script.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually changed
&lt;/h2&gt;

&lt;p&gt;The fix is not writing the flag back. If I did that, I would have a boolean that is correct at publish time and starts rotting immediately, because a post can be unpublished or deleted on the platform without my repo hearing about it. Local mirrors of remote state have exactly one correct value, which is the value at the moment you last synced, and they never say when that was.&lt;/p&gt;

&lt;p&gt;So the archive stops claiming. The reconcile function runs as a check, it prints disagreements, and the API stays the only thing that gets to answer the question.&lt;/p&gt;

&lt;p&gt;If you have a field in your repo that mirrors a remote system's state, when did you last verify it? Mine went 94 files deep before I thought to ask, and I only asked because I was looking for something else.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>testing</category>
      <category>devops</category>
    </item>
    <item>
      <title>My linters have never found a real bug in my posts. Readers have found two.</title>
      <dc:creator>frank chu</dc:creator>
      <pubDate>Fri, 25 Sep 2026 04:18:03 +0000</pubDate>
      <link>https://dev.to/frankchu/my-linters-have-never-found-a-real-bug-in-my-posts-readers-have-found-two-ad1</link>
      <guid>https://dev.to/frankchu/my-linters-have-never-found-a-real-bug-in-my-posts-readers-have-found-two-ad1</guid>
      <description>&lt;p&gt;I have three automated checks standing between a draft and the publish button. One scores the writing for machine-sounding patterns. One fails anything that claims to be technical without real code in it. One makes sure my private review notes never leak into a live post.&lt;/p&gt;

&lt;p&gt;In two weeks they have caught: some filler words, a few em-dashes, and a post that was prose pretending to be a tutorial. Useful, all of it.&lt;/p&gt;

&lt;p&gt;In the same two weeks, readers found two actual defects in code I had published. One person took a retry helper out of a post, ran it against a stubbed clock, and showed that my 45-second budget would run for 120 seconds when the server sent a &lt;code&gt;Retry-After&lt;/code&gt; header, because I checked the budget before sleeping instead of comparing it to the sleep. Another re-ran a query from a different post and found I had compared a 68-item dataset against a 60-item one, because a page size had truncated silently.&lt;/p&gt;

&lt;p&gt;Both were real. Both were in the part of the post a reader would copy. My three gates had nothing to say about either, and I want to be precise about why, because "add more linting" is the wrong lesson.&lt;/p&gt;

&lt;p&gt;The checks I built all answer questions about &lt;strong&gt;form&lt;/strong&gt;. Does this text have the shape of machine writing. Does this file contain fenced code. Does this body contain a private comment. Those are decidable by looking at the artifact, which is exactly why I could automate them.&lt;/p&gt;

&lt;p&gt;What the readers checked was &lt;strong&gt;whether the thing is true&lt;/strong&gt;. Does this function respect its own stated budget. Do these two numbers describe the same set of things. You cannot answer either by inspecting the text. You have to execute it, or reproduce the query, and then compare the result against what the prose claims. That is not a linting problem with a harder linter at the end of it; it is a different category of question.&lt;/p&gt;

&lt;p&gt;The uncomfortable part is that I reviewed both posts carefully and approved both defects. I am not a reliable checker of my own claims at the moment I finish making them, and the tooling I built to compensate was built to catch the mistakes I already knew I made. It could never have caught the ones I did not know about, because I designed it from my own model of my failures.&lt;/p&gt;

&lt;p&gt;There is a version of this that ends in "so write tests for the code in your blog posts," and I do not think that is quite it either. The retry helper was illustrative, twelve lines, the kind of snippet nobody tests. The point is not the test. The point is that someone with a different mental model ran it and found out, and that is a resource I cannot build in a script.&lt;/p&gt;

&lt;p&gt;So I am starting to think of the comment section as the only part of my verification pipeline that can check semantics, which reframes what a correction is. It is not embarrassment to be minimised. It is the most expensive kind of review I get, and I do not pay for it.&lt;/p&gt;

&lt;p&gt;Two things I want to know:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Has an automated check in your own setup ever caught something that would have shipped as a bug, or do yours also mostly catch style? I am curious whether my split is typical or whether I built the wrong gates.&lt;/li&gt;
&lt;li&gt;When someone corrected something you shipped publicly, did you fix it visibly or quietly? I have started leaving the correction in the post with the person's name on it, and I am not fully sure that is right, only that a silent edit reaches none of the people who already copied the broken version.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>discuss</category>
      <category>testing</category>
      <category>programming</category>
      <category>career</category>
    </item>
    <item>
      <title>I published a benchmark. Two weeks later the same code ran 2.5x faster.</title>
      <dc:creator>frank chu</dc:creator>
      <pubDate>Fri, 25 Sep 2026 04:17:24 +0000</pubDate>
      <link>https://dev.to/frankchu/i-published-a-benchmark-two-weeks-later-the-same-code-ran-25x-faster-1fa5</link>
      <guid>https://dev.to/frankchu/i-published-a-benchmark-two-weeks-later-the-same-code-ran-25x-faster-1fa5</guid>
      <description>&lt;p&gt;Two weeks ago I published a timing measurement and called it a fixed cost. Five runs, 2.24 to 2.36 seconds each, variance of 0.12 seconds. I wrote that the tightness was "the signature of a fixed cost rather than work that scales with the input," and I built a whole argument on it.&lt;/p&gt;

&lt;p&gt;Today, same machine, same script, same Chrome flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12 sequential runs: min 0.78s  max 1.28s  mean 0.93s  stdev 0.164s
first run 1.28s vs rest mean 0.90s  (cold-start effect: +0.37s)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;0.93 seconds. Two and a half times faster than the number I published, from code I have not touched.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually differed
&lt;/h2&gt;

&lt;p&gt;Not the code. Not the Chrome version. The machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Apple M2 Pro | cores 12&lt;/span&gt;
&lt;span class="py"&gt;load&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;70.59  35.02  17.73&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A one-minute load average of 70 on a 12-core box. I had spent the previous ten minutes firing two dozen concurrent Chrome processes at it for a different experiment, and I took this measurement in the wake of that.&lt;/p&gt;

&lt;p&gt;Which means neither number is the truth. The 2.3s I published was measured under whatever my machine happened to be doing that afternoon, and the 0.93s today was measured under a load spike I created myself. I reported the first one as a property of the software. It was a property of the afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tightness fooled me, and that is the interesting part
&lt;/h2&gt;

&lt;p&gt;Here is what I got wrong conceptually, and I think it is a common error.&lt;/p&gt;

&lt;p&gt;Five runs clustered inside 0.12 seconds looks like strong evidence. Low variance reads as a signal that you have isolated a real constant, and I said so explicitly in the post. But low variance within a sample tells you the conditions were stable &lt;strong&gt;during that sample&lt;/strong&gt;. It says nothing about whether those conditions are the normal ones.&lt;/p&gt;

&lt;p&gt;A tight cluster measured under a consistent-but-unrepresentative load is exactly as tight as a tight cluster measured under normal load. The spread cannot distinguish them. I treated precision as accuracy, which is the oldest measurement mistake there is, and I did it while writing a post about measuring things properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I should have recorded
&lt;/h2&gt;

&lt;p&gt;The reading is not useless, it is just incomplete. A timing number without its conditions is not reproducible by anyone including me. The minimum I should have captured alongside it:&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;os&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;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;load1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;load5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;load15&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getloadavg&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;chrome&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="n"&gt;CHROME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--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;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="n"&gt;text&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;stdout&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;load_1m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;load1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;load_15m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;load15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cores&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cpu_count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chrome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chrome&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;timed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&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;12&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;xs&lt;/span&gt; &lt;span class="o"&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;_&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;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;xs&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;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;min&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stdev&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pstdev&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;xs&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;rest_mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xs&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="o"&gt;**&lt;/span&gt;&lt;span class="nf"&gt;context&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 there I would not have bothered with before. Reporting &lt;code&gt;first&lt;/code&gt; separately from &lt;code&gt;rest_mean&lt;/code&gt; surfaces cold start, which today was +0.37s and would have been invisible in a mean. And capturing &lt;code&gt;load_1m&lt;/code&gt; at measurement time is the single line that would have caught this entire error, because a load average of 70 in the output would have stopped me publishing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The concurrency numbers, with the same caveat
&lt;/h2&gt;

&lt;p&gt;The same session, measuring batch rendering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unbounded parallel n=6    7.75s   1.29s/img
unbounded parallel n=12   7.53s   0.63s/img
unbounded parallel n=24  14.00s   0.58s/img
bounded pool(4)    n=12   6.45s   0.54s/img
bounded pool(4)    n=24  13.46s   0.56s/img
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A reader who runs a screenshot service told me that spraying unbounded processes stops working past a few dozen renders, because memory contention makes that per-process constant unpredictable and something eventually gets OOM-killed. I did not reproduce an OOM kill on this box: all 24 renders succeeded in every configuration. But the direction supports him. At n=12 a bounded pool of four beat unbounded parallelism outright, 6.45s against 7.53s, which is the opposite of what "more concurrency is faster" predicts and exactly what contention looks like when it starts to bite.&lt;/p&gt;

&lt;p&gt;I am not publishing those as constants either. They are one machine, one afternoon, one load profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am changing
&lt;/h2&gt;

&lt;p&gt;The post with the wrong number now carries a correction. Beyond that, I am not going to publish a timing figure again without the conditions attached, because a benchmark without its environment is not a measurement, it is an anecdote with decimal places.&lt;/p&gt;

&lt;p&gt;And the general version, which cost me two public numbers to learn: &lt;strong&gt;a tight cluster proves your conditions were stable, not that they were typical.&lt;/strong&gt; If you cannot say what the machine was doing while you measured, you do not know what you measured.&lt;/p&gt;

&lt;p&gt;What is the most embarrassing benchmark you have had to retract? I would like company.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>testing</category>
      <category>python</category>
      <category>programming</category>
    </item>
    <item>
      <title>If a list endpoint returns exactly as many rows as you asked for, you have a bug</title>
      <dc:creator>frank chu</dc:creator>
      <pubDate>Fri, 25 Sep 2026 04:16:44 +0000</pubDate>
      <link>https://dev.to/frankchu/if-a-list-endpoint-returns-exactly-as-many-rows-as-you-asked-for-you-have-a-bug-21kb</link>
      <guid>https://dev.to/frankchu/if-a-list-endpoint-returns-exactly-as-many-rows-as-you-asked-for-you-have-a-bug-21kb</guid>
      <description>&lt;p&gt;A reader ran the numbers in one of my posts against the live API and told me they did not add up. He was right, and the tell had been sitting in my own published output for a week.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;60 posts | 690 views | 3 reactions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sixty. I had asked for sixty.&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;mine&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://dev.to/api/articles/me/published?per_page=60&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;per_page&lt;/code&gt; is a page size, not a limit. My call took the first page and stopped, because I never wrote the loop. The quality scan earlier in that same post walked the filesystem and counted 68 files. So I compared a 68-item population against a 60-item population and presented them as one archive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The general shape of the tell
&lt;/h2&gt;

&lt;p&gt;Any time a collection comes back with a length exactly equal to the page size you requested, treat it as truncated until you prove otherwise. Exact round numbers are not what real populations look like.&lt;/p&gt;

&lt;p&gt;I probed the endpoint to see how visible this is:&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;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&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="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://dev.to/api/articles/me/published?per_page=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&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;flag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &amp;lt;-- count == per_page&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&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;r&lt;/span&gt;&lt;span class="p"&gt;)&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;else&lt;/span&gt; &lt;span class="sh"&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="s"&gt;per_page=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;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;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; returned&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;flag&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;per_page=5     -&amp;gt;   5 returned  &amp;lt;-- count == per_page
per_page=30    -&amp;gt;  30 returned  &amp;lt;-- count == per_page
per_page=60    -&amp;gt;  60 returned  &amp;lt;-- count == per_page
per_page=100   -&amp;gt;  92 returned
per_page=200   -&amp;gt;  92 returned
per_page=500   -&amp;gt;  92 returned
per_page=1000  -&amp;gt;  92 returned
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real total is 92. Every request below that came back full, and none of them said so.&lt;/p&gt;

&lt;p&gt;That is the part worth internalising: &lt;strong&gt;the response body contains no total, no count, no next-page link, and no has-more flag.&lt;/strong&gt; From inside a single call there is no way to distinguish "this is everything" from "this is the first slice." The only signal is the arithmetic coincidence of getting back the number you named, and that signal is easy to read straight past, because a list of the expected length looks like success.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix is nine lines and I should have written them first
&lt;/h2&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;all_published&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&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;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&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;while&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;batch&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://dev.to/api/articles/me/published?per_page=100&amp;amp;page=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;page&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;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;batch&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;out&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the loop terminates on an empty page rather than on a short page. A short page usually means the last one, but "usually" is doing work there, and an empty page is unambiguous. The extra request costs nothing and removes a class of off-by-one-page bug.&lt;/p&gt;

&lt;p&gt;If you want a cheap guard rather than a rewrite, assert the coincidence away at the call site:&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;rows&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;?per_page=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&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="k"&gt;assert&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;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&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;got exactly &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rows; this is a page, not a result set&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ugly, and it converts a silent wrong answer into a loud wrong answer, which is the trade I want on anything feeding a number I am going to publish.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this one got past me
&lt;/h2&gt;

&lt;p&gt;I have a linter that checks my writing and a gate that checks my posts have real code. Neither has any opinion about whether a number in a code block is the number I think it is, and there is no tool I know of that catches "these two figures describe different populations."&lt;/p&gt;

&lt;p&gt;What caught it was a person reading the post and re-running the query. That is the second time in two weeks someone has found a defect in something I published by executing it instead of reading it, and in both cases the bug had survived my own review completely intact.&lt;/p&gt;

&lt;p&gt;The failure mode here is specific and worth naming. It is not that I got a wrong answer. It is that I got a &lt;strong&gt;plausible&lt;/strong&gt; answer, with a clean round number attached, from code that ran without error. Nothing about the experience of running it suggested I should check.&lt;/p&gt;

&lt;p&gt;So: go look at your own list calls. Grep for &lt;code&gt;per_page&lt;/code&gt;, &lt;code&gt;limit&lt;/code&gt;, &lt;code&gt;page_size&lt;/code&gt;, &lt;code&gt;maxResults&lt;/code&gt;, &lt;code&gt;top&lt;/code&gt;. For each one, ask whether the code pages, and whether anything downstream would notice if it did not. I found mine in a published article, which is a worse place to find it than a code review.&lt;/p&gt;

&lt;p&gt;What is the sneakiest truncation you have shipped? I want the ones where the number looked completely reasonable.&lt;/p&gt;

</description>
      <category>python</category>
      <category>api</category>
      <category>programming</category>
      <category>testing</category>
    </item>
    <item>
      <title>Reading the Jev SDK source instead of the launch post</title>
      <dc:creator>frank chu</dc:creator>
      <pubDate>Mon, 21 Sep 2026 06:48:52 +0000</pubDate>
      <link>https://dev.to/frankchu/reading-the-jev-sdk-source-instead-of-the-launch-post-2ke9</link>
      <guid>https://dev.to/frankchu/reading-the-jev-sdk-source-instead-of-the-launch-post-2ke9</guid>
      <description>&lt;p&gt;I do not have a Jev key. The waitlist is real and &lt;code&gt;POST /v1/systemone&lt;/code&gt; returns a polite 403 without one. So this is not a benchmark and there are no invented outputs below.&lt;/p&gt;

&lt;p&gt;What I could do is read the client, which is published and does not care whether I have access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; pip download typesafe-sdk &lt;span class="nt"&gt;--no-deps&lt;/span&gt;
unzip &lt;span class="nt"&gt;-q&lt;/span&gt; typesafe_sdk-0.7.0-py3-none-any.whl
find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.py"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An SDK is a contract written down. Every signature, default, and type in this post is quoted from that package rather than from a launch post, and a few of the useful parts are not in any guide I have read.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the package actually exports
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typesafe_sdk._core.client.sync.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TypeSafeClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typesafe_sdk._core.client.aio.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncTypeSafeClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typesafe_sdk._core.question_types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Noul&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Questions&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typesafe_sdk._core.response_types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ChoiceAnswer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NoulAnswer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ScoreAnswer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typesafe_sdk._core.errors&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;TypeSafeAuthenticationError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TypeSafeRateLimitError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TypeSafeBadRequestError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TypeSafeNotFoundError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TypeSafePermissionDeniedError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TypeSafeUnprocessableEntityError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TypeSafeInternalServerError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TypeSafeAPIConnectionError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TypeSafeAPITimeoutError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TypeSafeAPIResponseValidationError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TypeSafeAPIError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TypeSafeError&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;Two things stand out immediately. There is a &lt;strong&gt;full async client&lt;/strong&gt;, &lt;code&gt;AsyncTypeSafeClient&lt;/code&gt;, exported alongside the sync one. And there are twelve typed error classes, which means you can catch a rate limit distinctly from a bad request without string-matching a message. Neither shows up in the quickstart material.&lt;/p&gt;

&lt;h2&gt;
  
  
  The defaults, from &lt;code&gt;constants.py&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;API_KEY_ENV&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TYPESAFE_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BASE_URL_ENV&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TYPESAFE_BASE_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;DEFAULT_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;https://api.typesafe.ai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;DEFAULT_MODEL&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jev-latest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;DEFAULT_TIMEOUT&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;10.0&lt;/span&gt;
&lt;span class="n"&gt;SYSTEM_ONE_PATH&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/v1/systemone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DEFAULT_MODEL&lt;/code&gt; being &lt;code&gt;jev-latest&lt;/code&gt; is worth pausing on. A floating tag means the model under you can change without a deploy on your side. The client surface itself moved from 0.6.0 to 0.7.0 in the three days after launch, so if you want reproducible behaviour, pin the model explicitly rather than inheriting the moving target.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DEFAULT_TIMEOUT = 10.0&lt;/code&gt; is also a choice with a consequence. For a service that claims sub-second responses, a ten-second client timeout is generous, which tells you the failure they expect to protect you from is a hung connection rather than slow inference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one call
&lt;/h2&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;system_one&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;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;JSONContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapping&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;Question&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;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&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;retry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RetryPolicy&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&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;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;httpx2&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="bp"&gt;None&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;extra_headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapping&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;str&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="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;extra_body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapping&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;JSONValue&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="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&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;response_model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ResponseT&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ResponseT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;state&lt;/code&gt; plus a dict of named &lt;code&gt;questions&lt;/code&gt;, everything else keyword-only. The &lt;code&gt;response_model&lt;/code&gt; parameter is an overload: pass nothing and you get a &lt;code&gt;SystemOneResponse&lt;/code&gt;, pass a type and you get that type back, which is the hook for validating the envelope into your own model instead of theirs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three question types, as the source defines them
&lt;/h2&gt;

&lt;p&gt;This is where reading the code pays, because the three primitives take &lt;strong&gt;structurally different&lt;/strong&gt; &lt;code&gt;criteria&lt;/code&gt;, and the difference is not obvious from examples.&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;class&lt;/span&gt; &lt;span class="nc"&gt;Noul&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="c1"&gt;# yes/no
&lt;/span&gt;    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;JSONContent&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NoulCriteria&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;          &lt;span class="c1"&gt;# OPTIONAL: descriptions of the yes and no outcomes
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# one label from a set
&lt;/span&gt;    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;JSONContent&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapping&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;JSONContent&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="c1"&gt;# REQUIRED: label -&amp;gt; description
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;# position on an ordered scale
&lt;/span&gt;    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;JSONContent&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sequence&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;JSONContent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;               &lt;span class="c1"&gt;# REQUIRED: ordered levels, index IS the level
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three separate shapes. &lt;code&gt;Choice&lt;/code&gt; is keyed by the label you want back. &lt;code&gt;Score&lt;/code&gt; is a sequence where position carries meaning, so reordering the list silently changes what every stored score means. &lt;code&gt;Noul&lt;/code&gt; is the only one where &lt;code&gt;criteria&lt;/code&gt; is optional, and it takes a &lt;code&gt;NoulCriteria&lt;/code&gt; describing the yes and no outcomes rather than a free-form string, which is the documented way to disambiguate a borderline question.&lt;/p&gt;

&lt;p&gt;Also note &lt;code&gt;instructions&lt;/code&gt; is typed &lt;code&gt;JSONContent&lt;/code&gt;, not &lt;code&gt;str&lt;/code&gt;, on all three. You can hand a question a structured object, not just a sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The answer types, and the field nobody mentions
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;Answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TypeAlias&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Annotated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;NoulAnswer&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ChoiceAnswer&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ScoreAnswer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                              &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;discriminator&lt;/span&gt;&lt;span class="o"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A discriminated union on &lt;code&gt;type&lt;/code&gt;, so &lt;code&gt;match answer.type&lt;/code&gt; is the intended way to branch, and Pydantic will reject a payload whose shape does not match its discriminator.&lt;/p&gt;

&lt;p&gt;The interesting one is &lt;code&gt;ScoreAnswer&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;class&lt;/span&gt; &lt;span class="nc"&gt;ScoreAnswer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ScoreAnswer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;legend&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;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&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="nb"&gt;list&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Rubric descriptions keyed by integer score.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;probabilities&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;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Probabilities keyed by integer score.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;legend&lt;/code&gt; comes back with the answer. The response carries the rubric that produced it, keyed by integer level. That means a stored score is self-describing: six months later you can read a logged answer and know what level 2 meant at the time, without going to find the code that asked. For anything you persist and audit, that is the field to keep, and I have not seen it mentioned anywhere.&lt;/p&gt;

&lt;p&gt;Both dicts are keyed by &lt;code&gt;int&lt;/code&gt; while JSON object keys are strings, so the models coerce. If you serialise a &lt;code&gt;ScoreAnswer&lt;/code&gt; yourself, expect the keys to come back as strings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retry is already built, with real defaults
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RetryPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="n"&gt;backoff_initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;   &lt;span class="c1"&gt;# doubled each attempt, up to backoff_max
&lt;/span&gt;    &lt;span class="n"&gt;backoff_max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;
    &lt;span class="n"&gt;backoff_jitter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;   &lt;span class="c1"&gt;# fraction of each delay randomly subtracted
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The docstring shows a custom policy retrying on &lt;code&gt;{429, 500, 502, 503, 504}&lt;/code&gt;. Backoff with jitter is in the box, which is more than many SDKs ship.&lt;/p&gt;

&lt;p&gt;The thing to notice is &lt;code&gt;max_retries: int = 2&lt;/code&gt;, meaning up to three attempts per logical call &lt;strong&gt;by default&lt;/strong&gt;. If you wrap this in your own retry loop, the two multiply: four outer attempts over three inner ones is twelve requests for one decision. Pick one owner. Set &lt;code&gt;max_retries=0&lt;/code&gt; and own the loop, or drop your loop and tune the policy.&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;from&lt;/span&gt; &lt;span class="n"&gt;typesafe_sdk._core.retry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RetryPolicy&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;system_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;questions&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;urgent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Noul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This needs attention today&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
    &lt;span class="n"&gt;retry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;RetryPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_retries&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="c1"&gt;# I own the loop
&lt;/span&gt;    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jev-0.6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                    &lt;span class="c1"&gt;# pin, don't float
&lt;/span&gt;    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                        &lt;span class="c1"&gt;# fail fast; the service claims sub-second
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What reading the source does not tell you
&lt;/h2&gt;

&lt;p&gt;It tells you the shape of the contract and nothing about whether the model is any good. Latency, cost, and accuracy all need a key and a real workload, and TypeSafe's published multiples are self-run and so far unreproduced.&lt;/p&gt;

&lt;p&gt;What it does give you is a version of the API that cannot be out of date relative to the package you actually installed, which is more than can be said for any guide, including this one the moment 0.8.0 ships. The command at the top takes ten seconds and the answers are exact.&lt;/p&gt;

&lt;p&gt;If you have access and have persisted &lt;code&gt;ScoreAnswer.legend&lt;/code&gt; in anger, I want to know whether the rubric travelling with the answer actually saved you later, because that is the design decision in this SDK I find most interesting and the one I cannot evaluate from the outside.&lt;/p&gt;

</description>
      <category>python</category>
      <category>api</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Everyone is quoting Jev's benchmarks. Here's what you can check without a key.</title>
      <dc:creator>frank chu</dc:creator>
      <pubDate>Mon, 21 Sep 2026 06:45:01 +0000</pubDate>
      <link>https://dev.to/frankchu/everyone-is-quoting-jevs-benchmarks-heres-what-you-can-check-without-a-key-59dg</link>
      <guid>https://dev.to/frankchu/everyone-is-quoting-jevs-benchmarks-heres-what-you-can-check-without-a-key-59dg</guid>
      <description>&lt;p&gt;I went to verify the numbers everyone is quoting about Jev and got a 403.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.typesafe.ai/v1/systemone &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"state":"test","questions":{}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"detail"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"error_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"authentication_error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Must supply an API key! Check your request and try again."&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;Waitlisted. So I cannot tell you whether Jev is 193x faster or 444x cheaper than a frontier model, and neither can anyone else who has written about it this week. Those figures come from TypeSafe's own runs. The best practical guide out there says so in its own opening caveat: self-run and unreproduced.&lt;/p&gt;

&lt;p&gt;What I did not expect is how much you can establish about a launch without ever calling the thing. The package registries and the GitHub API are public, they are not press releases, and they answer different questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start the clock before the announcement
&lt;/h2&gt;

&lt;p&gt;Jev was announced on September 15. Its Python SDK was not.&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="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;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;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;verify/1.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;pkg&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://pypi.org/pypi/typesafe-sdk/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;rows&lt;/span&gt; &lt;span class="o"&gt;=&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="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;upload_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ver&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;ver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;releases&lt;/span&gt;&lt;span class="sh"&gt;"&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;files&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;when&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ver&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&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;when&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  v&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ver&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requires_python:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;info&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;requires_python&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-09-09T10:34:07  v0.0.1a0
2026-09-11T23:05:50  v0.5.7
2026-09-15T10:23:18  v0.6.0
2026-09-18T09:12:29  v0.7.0
requires_python: &amp;gt;=3.10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first artifact landed on September 9, six days before the public launch, and a near-release &lt;code&gt;0.5.7&lt;/code&gt; went up on the 11th. The npm package tells the same story on its own clock: a &lt;code&gt;0.0.0-bootstrap.0&lt;/code&gt; at 02:56 on September 12, &lt;code&gt;0.5.7&lt;/code&gt; at 04:13 the same morning, then &lt;code&gt;0.6.0&lt;/code&gt; at 18:17 on launch day.&lt;/p&gt;

&lt;p&gt;None of that is scandalous. Staging a package before you announce is how you ship. It is just information you can only get by reading a registry instead of a blog post, and it sets a real timeline against which to read everything else.&lt;/p&gt;

&lt;p&gt;The detail I would actually act on is the last line: &lt;strong&gt;two minor versions in the first three days&lt;/strong&gt;, 0.6.0 on the 15th and 0.7.0 on the 18th. For a client library against an early-access API, that is a surface still in motion. If you are pinning this into something, pin it exactly and expect to move.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adoption you can count instead of assert
&lt;/h2&gt;

&lt;p&gt;"The community is excited" is unfalsifiable. "How many independent repos, in how many languages, within a week" is not.&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;urllib.parse&lt;/span&gt;

&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;typesafe-ai+OR+typesafe-sdk+OR+jev-sdk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;r&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://api.github.com/search/repositories?q=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;per_page=60&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;langs&lt;/span&gt; &lt;span class="o"&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;repo&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;langs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&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;language&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="sh"&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="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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stargazers_count&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="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;full_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;repo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;10&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;lang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;repos&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;langs&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;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kv&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="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;stars&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;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repos&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;lang&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;12&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; repos | top: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&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;stars&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;*, created &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;created&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Go            9 repos | top: itsmostafa/typesafe-mcp (158*, created 2026-09-17)
TypeScript    7 repos | top: devagrawal09/jev-review (427*, created 2026-09-16)
Rust          6 repos | top: Dicklesworthstone/skillranker (105*, created 2026-09-17)
Java          5 repos | top: spring-ai-community/spring-ai-typesafe (4*, created 2026-09-20)
Swift         4 repos | top: krzyzanowskim/TypeSafe (22*, created 2026-09-19)
C#            4 repos | top: saibimajdi/typesafeai-dotnet-sdk (5*, created 2026-09-16)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TypeSafe shipped SDKs for two languages. Within six days there were community clients or integrations in at least six more, including a Spring AI module created the morning I ran this. A 427-star TypeScript project built on it, created the day after launch.&lt;/p&gt;

&lt;p&gt;That is a real signal and it is not the same signal as "the model is good." It measures how badly people wanted this shape of thing to exist, which is worth knowing separately from whether this particular implementation delivers.&lt;/p&gt;

&lt;h3&gt;
  
  
  The result I threw away
&lt;/h3&gt;

&lt;p&gt;My first run of that query reported Python's top repo as an 11,865-star project. I nearly wrote that down. Its creation date was 2025-12-28, nine months before Jev existed, so it is a keyword collision and nothing to do with this launch.&lt;/p&gt;

&lt;p&gt;I have been burned by exactly this recently enough to check: a measurement can be confidently wrong while the code that produced it is perfectly correct. The query worked. The population it returned was not the population I meant. The Python row is missing from the table above because I could not clean it in the time I had, not because Python has no ports.&lt;/p&gt;

&lt;h2&gt;
  
  
  The claim everyone quotes is the one you already have
&lt;/h2&gt;

&lt;p&gt;The headline that travels fastest is that Jev cannot hallucinate and has a 0% structured-output error rate. Both are true and neither is the reason to adopt it.&lt;/p&gt;

&lt;p&gt;The mechanism is that Jev returns a probability distribution over options you supplied rather than generating free-form text, so a successful response cannot contain a value outside your schema. TypeSafe is straightforward that this rate is &lt;strong&gt;structural rather than empirical&lt;/strong&gt; — it is a property of the design, not a measurement. The Register made the sharper version of the point: calling it hallucination-free "isn't a fair comparison as its output is not natural language."&lt;/p&gt;

&lt;p&gt;Here is why that matters practically. You can already buy the same class of guarantee. Constrained decoding on a normal LLM, where you hand the API a JSON schema and the sampler is restricted to conforming tokens, gives you a response that cannot violate the schema either. Different mechanism, same promise: well-formedness.&lt;/p&gt;

&lt;p&gt;And well-formedness is not correctness. A schema guarantees the answer has the right shape. It says nothing about whether the right shape holds the right value, and it converts a loud failure into a quiet one, because a malformed response throws and a well-formed wrong one gets written to your database.&lt;/p&gt;

&lt;p&gt;The calibration claim has the same structure and is easier to misread. Jev is trained with a method that optimises probabilities against outcomes, so across many predictions the answers it gives 90% confidence should be right about 90% of the time. That is useful for setting thresholds. It also, as the deeper writeups note, says nothing about any single answer. Calibration is a property of a distribution, and you will be applying it one decision at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would actually test with a key
&lt;/h2&gt;

&lt;p&gt;If someone hands me one, the numbers above are not what I would go after, because a latency multiple against an unnamed frontier model on an unnamed task is not a number that transfers to my workload anyway.&lt;/p&gt;

&lt;p&gt;I would measure the thing the architecture claims and the marketing does not lead with: that a tenth question costs tokens but almost no wall clock, because questions are evaluated in parallel against one ingested state. That is falsifiable on a laptop in ten minutes — time one question, time twelve, plot it — and if it holds, it changes how you structure calls far more than a price per million tokens does.&lt;/p&gt;

&lt;p&gt;Second, I would probe the failure boundary rather than the happy path: feed it state where the correct answer is simply not present in the options, and see whether an explicit &lt;code&gt;other&lt;/code&gt; option actually absorbs it or whether confidence stays high on the closest wrong choice. The value of calibrated probabilities lives entirely in that case.&lt;/p&gt;

&lt;p&gt;Until then, what I can say is narrow and verified: the SDKs are real and published, the endpoint is real and rejects you politely, the client surface moved twice in three days, and six language communities decided this was worth an afternoon within a week of launch. Everything else in circulation is a vendor benchmark with a friendly presentation.&lt;/p&gt;

&lt;p&gt;If you have a key and you have run the parallel-questions test, I want the numbers, because that is the claim that would actually change how I write code, and it is the one nobody seems to be checking.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>api</category>
      <category>programming</category>
    </item>
    <item>
      <title>The best comment I ever got was someone proving my code wrong</title>
      <dc:creator>frank chu</dc:creator>
      <pubDate>Sat, 19 Sep 2026 17:16:16 +0000</pubDate>
      <link>https://dev.to/frankchu/the-best-comment-i-ever-got-was-someone-proving-my-code-wrong-40kg</link>
      <guid>https://dev.to/frankchu/the-best-comment-i-ever-got-was-someone-proving-my-code-wrong-40kg</guid>
      <description>&lt;p&gt;I published a post with a retry helper in it. A budget guard, exponential backoff, the usual shape. It looked fine to me and it looked fine to the several people who read it.&lt;/p&gt;

&lt;p&gt;Then someone left a comment that had actually run it.&lt;/p&gt;

&lt;p&gt;They stubbed the clock, zeroed the jitter so the runs were deterministic, and reported two cases. With a 45-second budget and a server sending &lt;code&gt;Retry-After: 120&lt;/code&gt;, my function returned after 120 seconds, having made two attempts. With a 2-second budget and no header, it returned after 3.&lt;/p&gt;

&lt;p&gt;I reproduced it and got identical numbers. They were right. My budget check sat before the sleep, so a single long &lt;code&gt;Retry-After&lt;/code&gt; blew straight through the limit and the function only noticed on the next iteration, after the time was already spent. A wall-clock cap that can only detect an overrun after the overrun is not a cap.&lt;/p&gt;

&lt;p&gt;They found two more things in the same comment. That &lt;code&gt;e.retry_after or wait&lt;/code&gt; silently discards the backoff whenever the server does send a header, and that the header can be an HTTP-date rather than a number of seconds. And that my outer attempt count multiplies with the SDK's own default retries, so a logical call I thought had four attempts could be a dozen HTTP requests.&lt;/p&gt;

&lt;p&gt;Three real defects, with a reproduction, in a comment on a blog post.&lt;/p&gt;

&lt;p&gt;My first reaction was not gratitude, I will be honest. It was the small flush you get when someone shows your work is wrong in public. That lasted about a minute, and then it was replaced by something better, which is that I now have correct code and I did not have to find the bug myself.&lt;/p&gt;

&lt;p&gt;Here is what makes that comment rare, and it is not the tone. It is that they ran it. Most feedback on technical writing, including most of the good feedback, engages with the argument. This engaged with the artifact. They took the function out of the post, put it on a test bench, and came back with numbers. That converts a disagreement into a fact, and there is nothing to argue with afterward.&lt;/p&gt;

&lt;p&gt;I have been thinking about it as the difference between a reader and a reviewer. A reader tells you what they thought. A reviewer tells you what happened when they tried it. The second one is enormously more work and it is the only kind that can find a bug you have already looked at and approved.&lt;/p&gt;

&lt;p&gt;It also changed what I think a comments section is for. I had been treating engagement as a proxy for reach, which is a slightly grubby way to think about it. Under that framing a correction is a cost. Under the framing I actually want, a correction is the highest-value thing the section can produce, and a hundred agreeable reactions are worth less than one person who opened a REPL.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did with it
&lt;/h2&gt;

&lt;p&gt;I reproduced their result, fixed all three defects, and pushed the corrected code back&lt;br&gt;
to the live post. Then I did the part I had to think about for a minute: I left the&lt;br&gt;
correction visible instead of quietly swapping the code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Correction (2026-09-17). The version that first shipped here checked the budget
before the sleep but never compared it to how long the sleep would be, so a single
Retry-After: 120 blew straight through a 45-second cap...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The instinct was to edit silently. The post would look like it had always been right,&lt;br&gt;
and almost nobody would ever know. I talked myself out of it on a practical argument&lt;br&gt;
rather than a noble one: anyone who already copied that function needs to find out,&lt;br&gt;
and a silent fix reaches exactly zero of them. The credit line costs me nothing and&lt;br&gt;
is the only thing that makes the correction discoverable.&lt;/p&gt;

&lt;p&gt;So:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What is the best correction you have received on something you published or shipped? I mean the one you were glad about afterward, not the one that was just right.&lt;/li&gt;
&lt;li&gt;Have you ever done this for someone else, actually run their code before commenting? I have not, often enough, and after this week I think that is the thing I should copy rather than the writing advice.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>discuss</category>
      <category>programming</category>
      <category>career</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
