<?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: Sam Chen</title>
    <description>The latest articles on DEV Community by Sam Chen (@codex_1135).</description>
    <link>https://dev.to/codex_1135</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%2F4062899%2F798086b5-2399-48f9-b7fe-b2723441ab97.png</url>
      <title>DEV Community: Sam Chen</title>
      <link>https://dev.to/codex_1135</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codex_1135"/>
    <language>en</language>
    <item>
      <title>Your Model Graded Its Own Homework</title>
      <dc:creator>Sam Chen</dc:creator>
      <pubDate>Sun, 11 Oct 2026 21:23:57 +0000</pubDate>
      <link>https://dev.to/codex_1135/your-model-graded-its-own-homework-43nb</link>
      <guid>https://dev.to/codex_1135/your-model-graded-its-own-homework-43nb</guid>
      <description>&lt;p&gt;A green log is not a finished patch. I start from that rule every single time. If the generator also wrote the test, you graded nothing.&lt;/p&gt;

&lt;p&gt;I keep a short catalog for free model drafts. Each entry lists a symptom, a cause, and a replacement. I do not need a new slogan to spot the miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I refuse to trust
&lt;/h2&gt;

&lt;p&gt;Free model access can draft a unified diff. A free server can run that diff one time. Neither option is allowed to define the expected behavior.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;Do you want a draft, or do you want a verdict? I want the draft, and I keep the verdict. I use those two options as a rehearsal bench only.&lt;/p&gt;

&lt;p&gt;I do not treat a chat reply as a specification. I do not treat a remote exit code as proof. A friendly log can still hide a fake oracle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 1: the model writes the oracle
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;The new tests mention the same names as the patch. They pass on the first remote run you try. You feel finished, and that feeling is the trap.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;One generator filled both sides of the check. The test encodes the implementation you just accepted. It cannot catch a wrong idea with a pretty shape.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;I write two fixture files before I ask for any code. One file holds the input object I care about. The other file holds the output I will accept.&lt;/p&gt;

&lt;p&gt;The model may not edit either fixture file. Could a stranger fail this test without your patch? If the answer is no, your oracle is fake.&lt;/p&gt;

&lt;p&gt;Write a stricter fixture, then ask the model again. Then rerun the compare before you trust the color. These commands are an unexecuted example, not a lab report.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; rehearsal/contract
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'{"n": 4}'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; rehearsal/contract/input.json
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'{"ok": true, "double": 8}'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; rehearsal/contract/expect.json
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; rehearsal/contract/expect.json &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"fixture ready"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the expected numbers to match your real rule. Do not let the model pick them after it sees the diff. Pair the files with a short note you wrote yourself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Behavior note&lt;/span&gt;

Input n is an integer from 0 to 20.
Return ok true only when n is even.
Return double as n times 2 when ok, else 0.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That note is the spec the model is allowed to see. If the note and the fixture disagree, fix the note first. Do not ask the model to reconcile your own contradiction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 2: fixtures arrive after the diff
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;The patch lands in the branch before any example. The sample numbers show up later in the same chat. Those numbers match the code you already wanted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;You let the result choose the requirement after the fact. Time order is the actual bug, not the syntax. A contract should be older than the candidate patch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;I record file times before I send any prompt. The gate fails when the fixture is newer than the patch. That rule feels strict, and it should feel strict.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'%Y %n'&lt;/span&gt; rehearsal/contract/expect.json rehearsal/patch.diff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On macOS I switch to &lt;code&gt;stat -f '%m %N'&lt;/code&gt; for the same check. I label this snippet as a local proposal, not a benchmark. Which file did you create first, the fixture or the diff?&lt;/p&gt;

&lt;p&gt;If you cannot tell, you do not have a contract yet. Stop the prompt and write the fixture in git first. A chat bubble is not a timestamp you can audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 3: the free box becomes the spec
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;The script runs on the free server and looks fine. The same script fails on your laptop five minutes later. The missing import was never written into your lockfile.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;An unnamed runtime quietly became your real design. Locale, timezone, and preinstalled packages hid the gap. You then copied host notes into the repo as requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;I pin the command, the workdir, and the expected files. The remote host may execute the candidate one time. It may not invent requirements I did not write down.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Free server may do&lt;/th&gt;
&lt;th&gt;You must own&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Run the candidate once&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;The command text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Print stdout and stderr&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;The fixture files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add a missing package&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Your lockfile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decide pass or fail&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Your local compare&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Store a secret or token&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Your redaction step&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If a package exists only on that host, I stop. I add it locally, or I drop the idea entirely. Does your lockfile explain every import the log used?&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 4: you skip the deleted lines
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;The viewer paints the new lines in a friendly green. A helper function vanished near the bottom of the file. The new test never called the path you just lost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;Your eyes follow additions because additions look like work. Generated patches delete quietly while you read the headline. Addition bias is how bounds checks disappear unnoticed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;I count removed lines before I read any new line. A deletion without a contract note is an automatic reject. I want a human sentence that explains each removal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff &lt;span class="nt"&gt;--numstat&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; candidate.py
git diff &lt;span class="nt"&gt;--unified&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;--&lt;/span&gt; candidate.py | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'^-'&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s1"&gt;'^---'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why did that line leave the file at all? If you cannot answer in one plain sentence, stop. Leave the branch dirty until the note exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 5: comments pretend to be a fix
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;The diff is long, but behavior barely moved at all. New comments restate the old function in softer words. The free server still prints the same wrong value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;Activity got mistaken for progress during a fast review. A verbose patch feels safer than a small sharp edit. The generator optimized for looking busy, not the fixture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;I reject patches whose comment lines dominate the behavior lines. Docs belong in the notes file when I asked for docs. Otherwise I want a smaller diff that moves the output.&lt;/p&gt;

&lt;p&gt;Did the output change, or did the narration change? Grade the fixture, not the paragraph under the function. A longer file is not a stronger fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run this gate after the remote log comes home
&lt;/h2&gt;

&lt;p&gt;This script is a proposal you can read today. I am not publishing timing numbers or success rates. It checks shape, notes, and a plain JSON compare.&lt;/p&gt;

&lt;p&gt;It does not deploy, install, or call a network. Treat every command below as unexecuted sample code. Adapt paths if your repo layout differs from this example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Local contract gate. Proposal, not a measured benchmark.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rehearsal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;NEED&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;input.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;expect.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;notes.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&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="s"&gt;REJECT &lt;/span&gt;&lt;span class="si"&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="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&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;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ROOT&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contract&lt;/span&gt;&lt;span class="sh"&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;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_dir&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing contract dir&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;NEED&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;box&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="nf"&gt;fail&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 &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="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;box&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expect.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;got_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ROOT&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;server_out.json&lt;/span&gt;&lt;span class="sh"&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;got_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing server output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;got_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&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;got&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output != fixture&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;notes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;box&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notes.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&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;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;notes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notes too thin&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;PASS local contract compare&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;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the remote stdout into &lt;code&gt;rehearsal/server_out.json&lt;/code&gt; before the run. Then invoke the gate and read the exit code yourself. A non-zero exit means reject, not a warmer prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 rehearsal/gate_patch.py
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"exit=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A pass means your output matched the fixture you wrote. A pass does not mean the patch is wise or safe. A pass does not mean the remote host was clean.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preflight before the prompt
&lt;/h3&gt;

&lt;p&gt;I run four checks before I ask for any draft.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confirm both fixture files exist and are non-empty.&lt;/li&gt;
&lt;li&gt;Confirm notes.md states the behavior in plain words.&lt;/li&gt;
&lt;li&gt;Scan the prompt text for keys before you paste.&lt;/li&gt;
&lt;li&gt;Send the contract, not your whole repository tree.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-nE&lt;/span&gt; &lt;span class="s1"&gt;'(AKIA|BEGIN PRIVATE KEY|password\s*=)'&lt;/span&gt; prompt.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"stop"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That sniff is a seatbelt, not a full secret scanner. If it prints stop, I rewrite the prompt without the secret. I never paste a live environment file to save time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a free model and a free server fit
&lt;/h2&gt;

&lt;p&gt;I ask for a patch against notes.md, not against a vibe. I send no secrets and no environment file with the ask. I run the candidate once on the free server option.&lt;/p&gt;

&lt;p&gt;I bring stdout home and compare it on my machine. MonkeyCode is the bench this rehearsal is written around. Free model access drafts the patch from the notes I wrote.&lt;/p&gt;

&lt;p&gt;The free server option runs that draft away from my laptop. I still own the contract, the fixture, and the reject. Remove the product name and the gate still stands.&lt;/p&gt;

&lt;p&gt;I am not stating a token quota in this article. I am not naming a model identifier or a hardware size. I am not promising how long any free offer stays up.&lt;/p&gt;

&lt;p&gt;Check the live offer before you plan a week around it. Terms move, and stale numbers become confident lies. A missing quota line is safer than a fabricated one.&lt;/p&gt;

&lt;p&gt;Is a fake quota more helpful than an honest gap? No, because a fake number schedules work you cannot finish. I would rather send you to the current page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this approach
&lt;/h2&gt;

&lt;p&gt;Skip this if you cannot read a diff line by line. Skip this if the change touches auth, payments, or private data. Skip this if you need a certified or reproducible release build.&lt;/p&gt;

&lt;p&gt;A free remote host is a rehearsal room, not a release gate. Skip this if your fixture says only "make it better." That sentence is a mood, not a contract you can test.&lt;/p&gt;

&lt;p&gt;Write the input, write the output, then ask for code. Teams with no human reviewer should not adopt this flow. The script can reject a mismatch, not a bad product idea.&lt;/p&gt;

&lt;p&gt;Judgment stays with a person who can own the merge. If that person is absent, do not automate the yes. Wait until a reviewer can read the deletions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I keep on the next pass
&lt;/h2&gt;

&lt;p&gt;I keep four questions on a card beside the keyboard.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Who wrote the oracle that this run claims to satisfy?&lt;/li&gt;
&lt;li&gt;Which file is older, the fixture or the candidate patch?&lt;/li&gt;
&lt;li&gt;What did the host add that my lockfile does not list?&lt;/li&gt;
&lt;li&gt;Which lines disappeared, and where is the note?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any answer is mush, I stop the merge right there. The draft can stay on the branch until the note exists. The verdict stays with me, not with the remote log.&lt;/p&gt;

&lt;p&gt;If you already keep fixtures in git, rehearse this once. Use a free model pass only to draft against your notes. Use one free server run only to collect the remote log.&lt;/p&gt;

&lt;p&gt;Then let the local script be the only grade you trust. That is the whole offer I am willing to make today. No countdown, no ranking claim, and no borrowed story.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>python</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Free Host Said Yes. Your Fence Card Is Still Blank.</title>
      <dc:creator>Sam Chen</dc:creator>
      <pubDate>Sat, 10 Oct 2026 17:48:38 +0000</pubDate>
      <link>https://dev.to/codex_1135/the-free-host-said-yes-your-fence-card-is-still-blank-4ok9</link>
      <guid>https://dev.to/codex_1135/the-free-host-said-yes-your-fence-card-is-still-blank-4ok9</guid>
      <description>&lt;p&gt;A free coding host is not a production fence. I treat a trial login as a risk, not as a vault. That shortcut turns into debt before the first merge.&lt;/p&gt;

&lt;p&gt;Do you want the short version before the long catalog? Write the fence card on your laptop before any send. If that card fails, the free box never sees the prompt.&lt;/p&gt;

&lt;p&gt;This piece is an anti-pattern catalog, not a product tour. Each item names a symptom, a root cause, and a replacement. I offer the checker as a proposal, not as a measured benchmark.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the card is for
&lt;/h2&gt;

&lt;p&gt;You have a coding task that a free model can draft. A free server can hold the scratch step off your laptop. That pair is useful, but it is not a vault or an SLA.&lt;/p&gt;

&lt;p&gt;I keep the exploratory step behind a fence card. The card does not trust a happy status page or a long chat. Would you merge a patch you cannot explain tomorrow?&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 1: One shared trial key
&lt;/h2&gt;

&lt;p&gt;The symptom is one free key shared across every chat tool. The root cause is a trial login used as a team identity. The replacement is a task id bound to one prompt file.&lt;/p&gt;

&lt;p&gt;Shared keys hide who sent the prompt in the first place. They also hide which client the pasted text belonged to. Can you name the owner of yesterday's session right now?&lt;/p&gt;

&lt;p&gt;Store the key in a local secret store, never in the repo. Rotate it when a teammate leaves the scratch workflow. A key in a dotfile will leak the moment you share a zip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 2: The prompt becomes the spec
&lt;/h2&gt;

&lt;p&gt;The symptom is a requirement that lives only in a chat scroll. The root cause is speed, because the answer made the thread into law. The replacement is a short task file checked in before any call.&lt;/p&gt;

&lt;p&gt;A chat scroll is not a reviewable spec for the next person. A task file is small, diffable, and deliberately boring to read. I want the next reviewer to open a file, not a vibe.&lt;/p&gt;

&lt;p&gt;Put the goal, the non-goals, and the stop rule in that file. If you cannot write the stop rule, you are not ready. Why would a remote host know your stop rule better than you?&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 3: Two clients, one scratch disk
&lt;/h2&gt;

&lt;p&gt;The symptom is two client notes sharing one server workspace. The root cause is reuse of a disk that stayed dirty. The replacement is one task, one empty workspace, and a wipe note.&lt;/p&gt;

&lt;p&gt;Leftover files are not harmless clutter on a scratch disk. They become the next prompt's context by accident. Would you leave two customers' notes in one unlocked drawer?&lt;/p&gt;

&lt;p&gt;Name the workspace after the task id before you copy files in. Refuse the run when a foreign task id is still on disk. Wipe is part of the task, not a chore you might remember later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 4: Trial host as a CI runner
&lt;/h2&gt;

&lt;p&gt;The symptom is a pull request job starting on the trial host. The root cause is spare-capacity thinking, and free looked like owned CI. The replacement keeps CI on a runner you administer yourself.&lt;/p&gt;

&lt;p&gt;Untrusted pull request code plus a shared host is a bad mix. I cannot cite an isolation promise for a trial box in this draft. If you need a hardened runner, this path is the wrong path.&lt;/p&gt;

&lt;p&gt;Do not forward webhooks at the free host to save a bill. A saved bill is a poor trade for an unreviewed remote shell. Who owns the blast radius when that shell runs a stranger's script?&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 5: Pins with no local resolver
&lt;/h2&gt;

&lt;p&gt;The symptom is a generated lockfile committed without a local resolve. The root cause is fluency, so a finished-looking file skipped the resolver. The replacement regenerates pins with your own package tool.&lt;/p&gt;

&lt;p&gt;A fluent lockfile can still name a package you never chose. Your resolver is the check, and the model output is only a draft. Did you run the install on a machine you actually control?&lt;/p&gt;

&lt;p&gt;Diff the regenerated pins against the model draft before you commit. If the diff is huge, stop and read the package names aloud. A huge pin diff is a question, not a gift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 6: A pool limit dressed up as a bug
&lt;/h2&gt;

&lt;p&gt;The symptom is a mid-file death that you treat as a code bug. The root cause is a missing local budget file. A pool limit then looks like a logic crash.&lt;/p&gt;

&lt;p&gt;The replacement writes a cap beside the task and stops on it. I will not invent a token count, a duration, or a machine size. Those numbers move, and a stale blog figure becomes a trap.&lt;/p&gt;

&lt;p&gt;Check the vendor page on the same day you plan the run. Write the cap as a plain integer you are willing to spend. When the run hits the cap, save state and stop the session.&lt;/p&gt;

&lt;p&gt;Guessing that the pool is infinite is how scratch work eats the day. A false crash can burn an afternoon that should have been a stop. Do you want a budget line, or another hour of fake debugging?&lt;/p&gt;

&lt;h2&gt;
  
  
  The replacement pattern
&lt;/h2&gt;

&lt;p&gt;The replacement pattern is a fence card, not a longer prompt. You fill seven lines in a local card file. A script refuses the send when any line is blank.&lt;/p&gt;

&lt;p&gt;The free model and the free server stay behind that refusal. Here is the card I want sitting next to the task file. Treat it as a proposal you can adopt, not as a lab result.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The card protects the next reviewer from a chat-only spec.&lt;/li&gt;
&lt;li&gt;The card protects a second client from a dirty scratch disk.&lt;/li&gt;
&lt;li&gt;The card protects your CI from a trial host you do not run.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;task_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2026-10-10-fence-01
&lt;span class="nv"&gt;workspace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;empty
&lt;span class="nv"&gt;clients&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;one
&lt;span class="nv"&gt;secrets_in_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no
&lt;span class="nv"&gt;ci_target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;owned-runner
&lt;span class="nv"&gt;budget_cap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;budget.txt
&lt;span class="nv"&gt;wipe_after&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seven lines keep the rule obvious to a reviewer. A blank line is a failed review, not a minor nit. If a line is blank, you are not ready for a remote draft.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the checker before the send
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fail closed on your laptop
&lt;/h3&gt;

&lt;p&gt;The script below is unexecuted example code for the local check. It reads the card file and the task file only. It does not open a network connection of any kind.&lt;/p&gt;

&lt;p&gt;This parser reads KEY=value lines and does not source the card. It is not a full secret scanner, so do not pretend otherwise. I am not shipping this as a tested release.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# Proposal only. Fail closed before any free-host prompt.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;card&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;fence&lt;/span&gt;&lt;span class="p"&gt;-card.env&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.md&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"missing card"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$prompt&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"missing task file"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

get&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"^&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;="&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"missing &lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="p"&gt;#*=&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;task_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;get task_id&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;workspace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;get workspace&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;clients&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;get clients&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;secrets_in_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;get secrets_in_prompt&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;ci_target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;get ci_target&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;budget_cap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;get budget_cap&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;wipe_after&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;get wipe_after&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$workspace&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"empty"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"workspace must start empty"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;3&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$clients&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"one"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"one client per scratch task"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;3&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$secrets_in_prompt&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"no"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"prompt must stay free of secrets"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;3&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ci_target&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"owned-runner"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"do not aim CI at the trial host"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;3&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$budget_cap&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"budget cap file missing"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;3&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$wipe_after&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"yes"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"plan the wipe before the run"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;3&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s1"&gt;'AKIA[0-9A-Z]{16}|-----BEGIN |api[_-]?key[[:space:]]*[:=]'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$prompt&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"prompt looks like it carries a secret marker"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;4
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .fence
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"task_id=&lt;/span&gt;&lt;span class="nv"&gt;$task_id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"card=&lt;/span&gt;&lt;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"prompt=&lt;/span&gt;&lt;span class="nv"&gt;$prompt&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;".fence/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;task_id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.decision"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"fence card passed for &lt;/span&gt;&lt;span class="nv"&gt;$task_id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the checker like this before you open a remote session. Create a one-line budget file before you expect a pass. A missing budget file is a failed card, not a warning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"set-your-cap"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; budget.txt
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x fence-card.sh
./fence-card.sh fence-card.env task.md
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"exit=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A non-zero exit means you stay on the laptop and fix the card. A zero exit means the card is complete, not that the code is correct. You still review the diff on a machine you trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the free step belongs
&lt;/h2&gt;

&lt;p&gt;I send the free step only after the checker prints a pass. The model may draft, and the server may run one scratch command. The laptop still owns the spec, the pins, and the final diff.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;MonkeyCode, for this draft, is relevant only after the card passes. The operator-supplied note is free model access plus a free server option. The operator also describes the project as open source.&lt;/p&gt;

&lt;p&gt;I am not linking a repository, because no primary URL was supplied here. I am not stating a quota, a chip type, a time box, or permanence. Read the current project docs before you plan around either option.&lt;/p&gt;

&lt;p&gt;If those docs disagree with this paragraph, trust the docs. A blog post is a workflow note, not a contract or a status page. Would you put a client export on a host you cannot describe?&lt;/p&gt;

&lt;p&gt;That pair can hold an exploratory draft and a scratch command. It should not hold customer data, production deploys, or shared CI. Silence in the docs is a stop sign, not a blank check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six questions before the send
&lt;/h2&gt;

&lt;p&gt;Use these six checks when you decide whether the send is allowed. One failed check blocks the send, even if the other five pass. I would rather lose a draft than mix two clients on one disk.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stay local when the task file is not already in git.&lt;/li&gt;
&lt;li&gt;Wipe first when the workspace does not start empty.&lt;/li&gt;
&lt;li&gt;Stop now when the prompt shows a secret marker.&lt;/li&gt;
&lt;li&gt;Do not retarget CI onto a host you do not administer.&lt;/li&gt;
&lt;li&gt;Do not start when the budget file is missing from disk.&lt;/li&gt;
&lt;li&gt;Do not start when you cannot wipe the workspace afterward.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These checks are a habit, not a certification of the remote host. A printed list still needs a human who will honor a failed row.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the card does not prove
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Leave the numbers on today's docs
&lt;/h3&gt;

&lt;p&gt;The grep line is a tripwire, not a certified secret scanner. A clever paste can still sneak past a short pattern list. Use a real scanner when your org already runs one.&lt;/p&gt;

&lt;p&gt;The card does not prove isolation on a remote host. It does not prove uptime for the remote host. It does not prove that a zero price will last.&lt;/p&gt;

&lt;p&gt;Free access can change, so keep a local fallback ready. I did not run a benchmark for this script. I did not time a model, and I did not measure a server.&lt;/p&gt;

&lt;p&gt;The checker only tests that the budget file exists. It does not parse the integer, so you still have to honor it. A file full of jokes is a pass, and that is a real limit.&lt;/p&gt;

&lt;p&gt;If you need numbers, measure your own task and date the note. Paste the date next to the figure so the next reader sees the age. An undated metric is how old limits sneak into new posts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should skip this path
&lt;/h2&gt;

&lt;p&gt;Skip this path when the data is regulated or under a client contract. Skip it when you need a stated service level or a fixed machine shape. Skip it when the team will not keep the card inside review.&lt;/p&gt;

&lt;p&gt;Also skip it if you cannot wipe the scratch workspace after the task. A free server you cannot reset is a shared disk with a login page. Are you comfortable reading that disk out loud in a retro?&lt;/p&gt;

&lt;h2&gt;
  
  
  A five-step local test
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Drop a fake key marker into the task file and confirm exit code four.&lt;/li&gt;
&lt;li&gt;Set the workspace value to dirty and confirm exit code three.&lt;/li&gt;
&lt;li&gt;Remove the budget file and confirm the script stops before a pass.&lt;/li&gt;
&lt;li&gt;Pass a clean card and confirm the decision file appears.&lt;/li&gt;
&lt;li&gt;Keep that file next to the task so review can see the refusal trail.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not point this test at production, because the pass is only local. That plan is enough to trust the gate you actually have. It is not enough to trust a remote host you have not read.&lt;/p&gt;

&lt;p&gt;Read the host policy on the same day you decide to use it. If the policy is silent on isolation, do not invent a promise. A quiet policy page is not the same thing as a guarantee.&lt;/p&gt;

&lt;h2&gt;
  
  
  Close the loop on your laptop
&lt;/h2&gt;

&lt;p&gt;Free model access can speed a draft you already scoped. A free server can hold scratch work after the card passes. Neither one replaces the card, the task file, or the wipe plan.&lt;/p&gt;

&lt;p&gt;I start with the card, and the remote step stays optional after that. If you already use that free model access, run this checker first. Then read today's limits in the project docs before you plan load.&lt;/p&gt;

&lt;p&gt;Keep the final merge on a repository you control and can revert. A remote draft is only a guest in your workflow. Your repo is the host, and the host sets the rules.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>testing</category>
      <category>security</category>
    </item>
    <item>
      <title>The Transcript Looked Done. You Still Have No Receipt.</title>
      <dc:creator>Sam Chen</dc:creator>
      <pubDate>Fri, 09 Oct 2026 14:16:09 +0000</pubDate>
      <link>https://dev.to/codex_1135/the-transcript-looked-done-you-still-have-no-receipt-4ha6</link>
      <guid>https://dev.to/codex_1135/the-transcript-looked-done-you-still-have-no-receipt-4ha6</guid>
      <description>&lt;p&gt;A clean transcript is not a review record. I want a local receipt before any free session touches my branch. If that receipt is empty, I do not share the work.&lt;/p&gt;

&lt;p&gt;Does a confident last reply count as proof? No, I keep seeing summaries outrun the diff. The chat can sound finished while the tree is still a guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hole I am naming
&lt;/h2&gt;

&lt;p&gt;I am not repeating my older stop-loop note. I am not repeating the allowlist note either. This catalog is about the transcript you keep after the model goes quiet.&lt;/p&gt;

&lt;p&gt;A transcript is only a story of tokens. A review record is a set of files you can rerun. I mix those two views when I am tired.&lt;/p&gt;

&lt;p&gt;Do you mix those two views as well? Yes, I still catch it in late reviews. A receipt is how I catch that mix.&lt;/p&gt;

&lt;p&gt;Free model access makes another turn feel cheap. A free server makes a scratch run feel disposable. Cheap and disposable are not the same as checked.&lt;/p&gt;

&lt;p&gt;I still need the receipt on my laptop. No news page was attached to this draft. I will not invent a headline, a score, or a quota.&lt;/p&gt;

&lt;p&gt;If a number is missing here, that is on purpose. Stale product figures can waste a whole sprint. I would rather show the check than a stale number.&lt;/p&gt;

&lt;h2&gt;
  
  
  What sits in the receipt
&lt;/h2&gt;

&lt;p&gt;I keep four files next to the diff. They are plain text, and they are boring. Boring is what I want on a Friday merge.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;prompt.txt&lt;/code&gt; holds the exact text I am willing to send.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;claim-ledger.txt&lt;/code&gt; maps each claim to a file and a command.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;replay.txt&lt;/code&gt; stores the command, the exit line, and a log hash.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;receipt.sha256&lt;/code&gt; names those files so a later edit is obvious.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these files makes the model correct. They make my shortcut visible to a reviewer. That is the whole point of this catalog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 1: The summary replaces the diff
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;The pull request text is the model's closing paragraph. Reviewers reply to tone, and nobody names a line. The bug can hide under a smooth ending.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;The chat is ordered by time, and the diff is not. I let the easier view win the review. Have you merged a paragraph you never opened?&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;I write the ledger before I open the request. Each claim points at one path and one check. If I cannot point, I delete the claim.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;claim: empty names are rejected
file: src/parser.py
check: python -m unittest tests.test_parser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask yourself this before you paste a summary. Can a teammate run the check without opening the chat? If the answer is no, the summary is still a story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 2: Price becomes a privacy control
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;A key, a customer id, or a raw trace sits in the prompt. The word free made that paste feel small. A small paste can still become a large leak.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;I treated a billing label as a safety boundary. A billing label does not redact my files. Who told me that price was a control?&lt;/p&gt;

&lt;p&gt;Nobody told me that, and I just hoped. Hope does not redact a prompt file either. I still have to run the search myself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;I run a loud search before I send anything. This is not a full secret scanner at all. It catches the obvious leaks I keep repeating.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# proposal - unexecuted in this article&lt;/span&gt;
rg &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'api[_-]?key|secret|password|BEGIN '&lt;/span&gt; prompt.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A hit means I stop and rewrite the sample. I use fake values instead of live ones. I never send a live token into a free model box.&lt;/p&gt;

&lt;p&gt;Someone with a mandated scanner should keep that scanner. Do not swap a policy tool for one search line. This pattern is a tripwire, not a program of record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 3: The model invents a missing field
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;A tool blob lacks a key the patch still uses. The chat then fills that gap in prose. The code branches on a field nobody returned.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;I trusted the shape I hoped to see. I did not pin the shape I actually got. Hope is not a schema I can ship.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;I record observed keys, and I leave gaps empty. I do not ask the model to invent fields. A missing key means stop, then a fresh command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# proposal, not a captured production trace&lt;/span&gt;
&lt;span class="na"&gt;tool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;repo.status&lt;/span&gt;
&lt;span class="na"&gt;observed_keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;branch&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;dirty&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;missing_keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ahead&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the action line says stop, I do not merge. I fetch status with a command I can read. Then I edit that status note by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 4: Comments stand in for a test
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;The diff is mostly comments and renamed variables. The claim says bugfix, but the test hash did not move. The chat still calls that change a fix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;Comments feel like progress inside a long chat. Tests feel like friction, so the feeling wins. I refuse that trade on any branch I share.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;A bugfix claim must name a test path. Same hash as yesterday means the claim is void. I either write the test or I drop the label.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# proposal - unexecuted here&lt;/span&gt;
&lt;span class="nb"&gt;sha256sum &lt;/span&gt;tests/test_parser.py
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; tests/test_parser.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which of those two did I actually finish? The saved hash will answer that question cleanly. A feeling in the chat will not answer it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 5: Yesterday's log becomes the prompt
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;I resend the full chat so the next turn has context. The prompt then holds dead ends and reversed decisions. Old secrets can ride back in with that log.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;I used the provider as my working notebook. The provider is not my system of record. A free model will not remember my correction for me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;I keep a twenty-line digest instead of the novel. The digest lists the decision and the rejected option. I hash it, and I send the digest only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;decision: reject empty names in parser
rejected: coerce empty names to guest
ledger: claim-ledger.txt
stop: do not add a network call
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Would I want that digest in the repo later? If I would not, I should not send the longer log. The longer log is how old mistakes return.&lt;/p&gt;

&lt;h2&gt;
  
  
  The order I follow
&lt;/h2&gt;

&lt;p&gt;I follow the same order on every branch. I do not reorder it because a session feels friendly. A friendly session is still not a receipt.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I write prompt.txt with fake sample values only.&lt;/li&gt;
&lt;li&gt;I write the claim ledger before I ask for a patch.&lt;/li&gt;
&lt;li&gt;I run the redaction search and stop on any hit.&lt;/li&gt;
&lt;li&gt;I ask for a patch only after those files exist.&lt;/li&gt;
&lt;li&gt;I save the command and the exit line in the replay file.&lt;/li&gt;
&lt;li&gt;I run the receipt script and read every bad exit.&lt;/li&gt;
&lt;li&gt;I read the diff myself before I share the branch.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That order is the replacement pattern for this catalog. Each step leaves a file or an exit code. If a step has neither, I am still in the anti-pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  The check I run before a share
&lt;/h2&gt;

&lt;p&gt;Here is the bundle I keep beside the branch. It is a proposal, not a timed benchmark. Do not quote this script as a measured result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# receipt-check.sh - local check before share&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

need&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="o"&gt;}&lt;/span&gt;

need prompt.txt
need claim-ledger.txt
need replay.txt

&lt;span class="k"&gt;if &lt;/span&gt;rg &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'api[_-]?key|secret|password|BEGIN '&lt;/span&gt; prompt.txt&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'redaction hit'&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;3
&lt;span class="k"&gt;fi

if &lt;/span&gt;rg &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'^claim:.*bugfix'&lt;/span&gt; claim-ledger.txt &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; rg &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'^test:'&lt;/span&gt; claim-ledger.txt &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'bugfix claim without test line'&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;4
  &lt;span class="k"&gt;fi
fi

&lt;/span&gt;&lt;span class="nb"&gt;sha256sum &lt;/span&gt;prompt.txt claim-ledger.txt replay.txt &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; receipt.sha256
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'receipt ok'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I invoke it from the worktree, not from memory. A bad exit means I stay local and fix the file. I do not negotiate with that exit code in chat.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash receipt-check.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit 2 means a required file is missing or empty. Exit 3 means the prompt search hit a secret-like string. Exit 4 means a bugfix claim has no test line.&lt;/p&gt;

&lt;p&gt;I fix the file that failed, then I run the script again. I do not negotiate with the exit code in chat. A model apology is not an exit code.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I read the exits
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Exit&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Next move&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Crude checks passed on the three files&lt;/td&gt;
&lt;td&gt;Read the diff once more, then share&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A required file is missing or empty&lt;/td&gt;
&lt;td&gt;Write the file before any session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Prompt search hit a secret-like string&lt;/td&gt;
&lt;td&gt;Strip it and use a fake sample&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Bugfix claim without a test line&lt;/td&gt;
&lt;td&gt;Add a test path or drop the claim&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This table is a rule for my own branches. It is not a service level from any vendor. If your team needs signatures, this table is too small.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the free options fit
&lt;/h2&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;I mention MonkeyCode once, as one optional workbench. It offers free model access and a free server option. I do not name a model in this draft.&lt;/p&gt;

&lt;p&gt;I do not state a token quota or a duration. An outreach note mentioned a free token pool. I will not repeat an unverified figure here.&lt;/p&gt;

&lt;p&gt;No primary page was attached for me to check. Stale quotas are how teams plan the wrong week. Confirm the current docs yourself before you depend on them.&lt;/p&gt;

&lt;p&gt;The workflow stays on my laptop until the receipt check passes. I write the prompt file, then I run the script. Only then do I ask a free model for a patch.&lt;/p&gt;

&lt;p&gt;A free server can run the command stored in the replay file. I still commit the receipt next to the diff. The session log does not become the record.&lt;/p&gt;

&lt;p&gt;If your team already hashes prompts, try this check on one branch. Tell me which table row feels too strict. That is the only ask I will make here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this
&lt;/h2&gt;

&lt;p&gt;Do not use this script as a compliance archive. It has no signature, no clock sync, and no reviewer identity. A regulated repo needs a heavier trail than this.&lt;/p&gt;

&lt;p&gt;Do not use the check to hide a diff you cannot read. The script checks that files exist and look non-empty. It does not understand your parser or your threat model.&lt;/p&gt;

&lt;p&gt;Do not point the search at live production secrets. Use a fake sample when you test the pattern. I mean that as a hard stop, not a tip.&lt;/p&gt;

&lt;p&gt;Skip the free server when the job needs private paths. Customer data and long-lived credentials stay off that box. Keep those runs on a machine you already trust.&lt;/p&gt;

&lt;p&gt;The free option is for scratch work you can lose. If losing the run would hurt, do not start it there. A missing receipt is cheaper than a leaked trace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits I am willing to say out loud
&lt;/h2&gt;

&lt;p&gt;The search pattern misses secrets that look like normal words. The ledger can still be filled with confident lies. I can hash a bad prompt and still be wrong.&lt;/p&gt;

&lt;p&gt;The check raises the cost of skipping a review step. It does not make me honest by itself. I still have to read the diff with my own eyes.&lt;/p&gt;

&lt;p&gt;I did not execute these commands against a live product. Treat every block as a proposal until you run it. Verify current product terms before you plan a sprint.&lt;/p&gt;

&lt;p&gt;Terms move, and my files should still make sense. That is the bar I am willing to keep. If the bar feels low, add a signature and keep the files.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>testing</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Free Loop Kept Going. I Never Set a Stop.</title>
      <dc:creator>Sam Chen</dc:creator>
      <pubDate>Thu, 08 Oct 2026 09:53:58 +0000</pubDate>
      <link>https://dev.to/codex_1135/the-free-loop-kept-going-i-never-set-a-stop-1h76</link>
      <guid>https://dev.to/codex_1135/the-free-loop-kept-going-i-never-set-a-stop-1h76</guid>
      <description>&lt;p&gt;A free model reply is not a release decision. A free server log is not an audit trail either. I still want a local fence before any agent runs.&lt;/p&gt;

&lt;p&gt;That fence sits on the branch I plan to merge. I do not trust a scratch host to invent it later. If the stop is missing, the green line is theater.&lt;/p&gt;

&lt;p&gt;This is a composite review rule, not a measured outage report. I am not timing a cluster for this post. I am writing the stop I want in git.&lt;/p&gt;

&lt;p&gt;Does a green free run mean the patch is safe to merge? It does not, and I want that said first. It only means some host answered a prompt I sent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why free access keeps lying to me
&lt;/h2&gt;

&lt;p&gt;Free model access feels like permission to wander. I add another file because the call looks cheap. I retry a vague prompt and call the result research.&lt;/p&gt;

&lt;p&gt;Vibe-coded patches are filling review queues this season. The model often sounds sure of the fix. The run record is still empty of stops.&lt;/p&gt;

&lt;p&gt;Would I ban a free server for scratch work? No. I would ban unbounded use of that server. A scratch host can help, then it must stop.&lt;/p&gt;

&lt;p&gt;The debt is not the model voice. The debt is the missing fence around retries, context, secrets, and model identity. Those four failures stack quietly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 1: Retry until something looks green
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The same file shows three overlapping edits.&lt;/li&gt;
&lt;li&gt;Logs repeat one tool name without a new goal.&lt;/li&gt;
&lt;li&gt;A flaky check passes only on the final try.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;I treated free calls as an endless budget. The price felt like zero, so I never set a max. The loop then chased a green line, not a reviewable diff.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;Cap attempts in the runner before the first call. Exit when the cap hits. Do not ask the model whether it feels done.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Proposal only. This cap is local policy, not a benchmark.
&lt;/span&gt;&lt;span class="n"&gt;MAX_ATTEMPTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;allow_attempt&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;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&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="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempt fence: bad counter&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;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;MAX_ATTEMPTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempt fence: stop and read the diff&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two is my personal policy, not a law of nature. You can pick three if your suite is slow. Write the number down before you prompt.&lt;/p&gt;

&lt;p&gt;What should the runner do after that exit? Leave the worktree dirty and visible to me. Do not auto-commit, and do not open a pull request.&lt;/p&gt;

&lt;p&gt;If I raise the cap mid-run, I have restarted the anti-pattern. The new number belongs in a new commit. Reviewers should see the change of mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 2: Upload the world because the call is free
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Answers cite paths I deleted last week.&lt;/li&gt;
&lt;li&gt;Latency jumps without a real code change.&lt;/li&gt;
&lt;li&gt;Two files in the bundle disagree on one type.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;I skipped a context budget and called it thorough. Free access is not a design for relevance. More files often add noise, not truth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;Build a short manifest, then count bytes on disk. Refuse the send when the local cap breaks. Send a map of files, not the whole world.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Unexecuted teaching example. Tune the byte cap locally.&lt;/span&gt;
find src tests &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="se"&gt;\(&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.py'&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.md'&lt;/span&gt; &lt;span class="se"&gt;\)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/fence-manifest.txt
&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /tmp/fence-manifest.txt&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'$1 &amp;gt; 80000 { print "bundle fence"; exit 2 }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eighty thousand bytes is a classroom number, not a vendor quota. I want that refusal on my laptop first. The free server should never see a rejected bundle.&lt;/p&gt;

&lt;p&gt;Should tests enter the bundle by default? Sometimes yes, when they explain intent. Fixtures with live customer rows should not. I keep a deny file beside the manifest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# deny.txt — paths that never leave the machine
.env
.env.*
secrets/
fixtures/live/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The deny file is plain text on purpose. I can diff it like any other review. A hidden client toggle would fail that simple test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 3: Treat the free session as a vault
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A key shape shows up in a shared transcript.&lt;/li&gt;
&lt;li&gt;A teammate can replay the prompt from logs.&lt;/li&gt;
&lt;li&gt;Rotation starts in chat instead of the secret store.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;I confused a free server option with a private enclave. Convenience is not custody of my keys. The host may log prompts for its own reasons.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;Scan the outbound bundle for obvious secret shapes. Block the upload on the first match. Then read the bundle anyway, because scanners miss novel formats.&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;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;PATTERNS&lt;/span&gt; &lt;span class="o"&gt;=&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;AKIA[0-9A-Z]{16}&lt;/span&gt;&lt;span class="sh"&gt;"&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;-----BEGIN &lt;/span&gt;&lt;span class="sh"&gt;"&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;(?i)api[_-]?key\s*=&lt;/span&gt;&lt;span class="sh"&gt;"&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;(?i)secret[_-]?key\s*=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&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;pattern&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;PATTERNS&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;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redaction fence: secret shape in bundle&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;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is this a full data-loss program? It is not, and I will not pretend. It catches the paste mistakes I worry about most. New token formats will still slip through.&lt;/p&gt;

&lt;p&gt;I also refuse to send shell history to any scratch host. History is full of one-off exports. Those lines look harmless until a key is sitting there.&lt;/p&gt;

&lt;p&gt;Who owns the deny file on a team? A named reviewer, not the agent. If nobody owns redaction, do not use this flow at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 4: Swap the model when the free path blips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Monday notes name one model id.&lt;/li&gt;
&lt;li&gt;Wednesday's patch reads like another writer.&lt;/li&gt;
&lt;li&gt;Style checks fail while behavior checks still pass.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;I treated free model access as a stable contract. The client was allowed to pick whatever was up. I never stored a pin in the run record.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;Require a model id in a local policy file. If the server cannot echo that id, stop the job. Do not improvise a cousin model to save the demo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# run-fence.yml — local policy, not a published quota&lt;/span&gt;
&lt;span class="na"&gt;model_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;set-by-operator"&lt;/span&gt;
&lt;span class="na"&gt;max_attempts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="na"&gt;max_bundle_bytes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80000&lt;/span&gt;
&lt;span class="na"&gt;fail_closed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I will not invent a model name for you in this note. Pin the id your current docs actually list. If that id disappears, fail closed and reread the docs.&lt;/p&gt;

&lt;p&gt;How do I check the echo without a stale URL in print? I keep the HTTP path out of this article. A copied endpoint rots faster than the rule. Fill the path from the page you trust today.&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;echo_matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pin&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;echoed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&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;pin&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;pin&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;set-by-operator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model fence: pin is still a placeholder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pin&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="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;echoed&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A placeholder pin must never count as a match. That guard looks petty until review day. A surprise substitute is a different experiment, so stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The local fence, step by step
&lt;/h2&gt;

&lt;p&gt;I run this as a preflight, before any scratch host is called. Each step is local. Each failure exits nonzero. None of these steps ask the model for permission.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read &lt;code&gt;run-fence.yml&lt;/code&gt; and reject a placeholder model id.&lt;/li&gt;
&lt;li&gt;Build the manifest, then subtract every path in &lt;code&gt;deny.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Fail if bundle bytes exceed &lt;code&gt;max_bundle_bytes&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run the secret scan and stop on the first hit.&lt;/li&gt;
&lt;li&gt;Increment the attempt counter and stop at &lt;code&gt;max_attempts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Call the scratch host only after those checks pass.&lt;/li&gt;
&lt;li&gt;Compare the echoed model id with the pin, then stop on drift.&lt;/li&gt;
&lt;li&gt;Write a tiny report file and leave the merge decision to me.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Proposal. I have not executed this as a published bench.
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ready_to_review&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempts&lt;/span&gt;&lt;span class="sh"&gt;"&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;report&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_attempts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secret_scan&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;pass&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bundle_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&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;report&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_bundle_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model_echo&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;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model_pin&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;If &lt;code&gt;ready_to_review&lt;/code&gt; is false, I do not open the pull request. I fix the fence or the bundle first. I do not ask the model to talk me out of a red check.&lt;/p&gt;

&lt;p&gt;Where does the report live? Next to the diff, in the branch, as a small text file. Chat scrollback is not the record. A future reviewer cannot see my closed tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a free server is allowed to help
&lt;/h2&gt;

&lt;p&gt;MonkeyCode is one host I can point at this fence. It offers free model access and a free server option for a scratch run.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;I am not stating a token quota, a chip type, or a duration. I am not stating that the free option is permanent. I have not published latency numbers here. If the current docs disagree with this paragraph, believe the docs.&lt;/p&gt;

&lt;p&gt;What do I send that host? Only a bundle that passed the deny list and the secret scan. What do I require back? An echoed model id that matches my pin. What do I do on a mismatch? I stop, then I pick a pin the docs still list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Sketch only. Set FENCE_MODEL from run-fence.yml yourself.&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FENCE_MODEL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"missing pin"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FENCE_MODEL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"set-by-operator"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"local fence ready pin=&lt;/span&gt;&lt;span class="nv"&gt;$FENCE_MODEL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Would I call this path a production deploy? I would not. It is a scratch path with brakes on it. Production still needs your normal CI, review, and secret store.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;I require locally&lt;/th&gt;
&lt;th&gt;A free server may do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Who sets the stop?&lt;/td&gt;
&lt;td&gt;The fence file in git&lt;/td&gt;
&lt;td&gt;Nothing, until I upload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where do secrets live?&lt;/td&gt;
&lt;td&gt;The secret store only&lt;/td&gt;
&lt;td&gt;Nowhere in the prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which model ran?&lt;/td&gt;
&lt;td&gt;The pinned id&lt;/td&gt;
&lt;td&gt;Echo that id or fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What does green mean?&lt;/td&gt;
&lt;td&gt;Tests I trust already ran&lt;/td&gt;
&lt;td&gt;A sample, not a merge vote&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What if the host blips?&lt;/td&gt;
&lt;td&gt;Fail closed&lt;/td&gt;
&lt;td&gt;No silent model swap&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Can the free server be my only CI? No, and I do not want that shortcut. Can it be a first pass before CI? Yes, if the fence file is in the same commit. Reviewers should see the policy with the patch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should skip this approach
&lt;/h2&gt;

&lt;p&gt;Skip this if you need a signed availability contract today. Free access is an option, not an SLA I can hand an auditor. I will not dress it up as one.&lt;/p&gt;

&lt;p&gt;Skip this if production keys live in the repo and nobody owns redaction. A sample regex is not your compliance boundary. Fix ownership before you automate uploads.&lt;/p&gt;

&lt;p&gt;Skip this if you need a fair public benchmark. I gave no hardware notes and no latency table. Anyone inventing those numbers is guessing, including me.&lt;/p&gt;

&lt;p&gt;Use it if you maintain a small service and want a cheaper scratch loop. Use it if reviewers already read diffs and you only need a hard stop. The fence is the product of this workflow, not the host.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I refuse to count as proof
&lt;/h2&gt;

&lt;p&gt;A calm paragraph from the model is not proof. A timestamp on a free server is not proof. A green badge from an unpinned model is not proof either.&lt;/p&gt;

&lt;p&gt;Proof, for this rule, is boring and local. Attempts stayed under the cap I wrote down. The secret scan exited clean on the bundle. The echoed model id matched the pin I set.&lt;/p&gt;

&lt;p&gt;Then I still read the diff myself. The fence only earns a review, not a merge. If I skip that read, I have rebuilt the original anti-pattern with extra files.&lt;/p&gt;

&lt;p&gt;If you want a scratch host for that first pass, start from the current MonkeyCode docs for free model access and the free server option. Pin an id your docs name today. I would rather hear what the fence blocked than hear that the chat felt smart.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>testing</category>
      <category>security</category>
    </item>
    <item>
      <title>Bring a Kill Card or Leave the Scratch Host Alone</title>
      <dc:creator>Sam Chen</dc:creator>
      <pubDate>Thu, 08 Oct 2026 07:41:19 +0000</pubDate>
      <link>https://dev.to/codex_1135/bring-a-kill-card-or-leave-the-scratch-host-alone-4aha</link>
      <guid>https://dev.to/codex_1135/bring-a-kill-card-or-leave-the-scratch-host-alone-4aha</guid>
      <description>&lt;p&gt;A free server is a scratch lane, not a release gate. Free model access is a budgeted probe, not a blank check. I keep both ideas in one fence file.&lt;/p&gt;

&lt;p&gt;Do you keep a fence, or only a chat log? What dies first when the free box disappears tonight?&lt;/p&gt;

&lt;h2&gt;
  
  
  The point I will not soften
&lt;/h2&gt;

&lt;p&gt;Speed is useless if the session cannot be killed. A green page on a free box proves almost nothing. It does not prove secrets stayed off the box.&lt;/p&gt;

&lt;p&gt;It does not prove your laptop can replay the change. Are you debugging the app, or a host that vanished?&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am willing to claim
&lt;/h2&gt;

&lt;p&gt;I want a scratch lane when the question is still small. Disclosure: This article was prepared as part of MonkeyCode's product outreach. That brief names free model access and a free server option.&lt;/p&gt;

&lt;p&gt;The same brief calls the project open source. I still verify the repo license before I depend on it.&lt;/p&gt;

&lt;p&gt;I do not have a live quota sheet in front of me. I will not invent model names, caps, or uptime. Check the project docs before you plan a demo.&lt;/p&gt;

&lt;p&gt;Docs move, and a blog number goes stale fast. A stale cap in a post is worse than no cap. Would you bet a demo on a number I refused to invent?&lt;/p&gt;

&lt;h2&gt;
  
  
  How this catalog is built
&lt;/h2&gt;

&lt;p&gt;Each item below has a symptom, a cause, and a fix. I want you to steal the fix, not the slogan. Would you merge a patch you cannot replay tomorrow?&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern: no step meter
&lt;/h2&gt;

&lt;p&gt;Symptom: the agent edits files long after the task ends. You look up and the branch is a junk drawer.&lt;/p&gt;

&lt;p&gt;Root cause: the prompt never named a maximum step count. Free model access makes that omission feel cheap. Cheap is not the same thing as safe.&lt;/p&gt;

&lt;p&gt;Replacement: put max_steps in a card before you prompt. I stop the run when the card hits that number. Do you stop, or do you hope the model gets bored?&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern: secrets hitch a ride
&lt;/h2&gt;

&lt;p&gt;Symptom: a dotenv file sits in the folder you upload. The free server boots, and the key boots with it.&lt;/p&gt;

&lt;p&gt;Root cause: you treated a scratch box like a private vault. A free option does not change secret handling rules.&lt;/p&gt;

&lt;p&gt;Replacement: deny env files and key paths in the card. I refuse the session if those paths exist in the tree. Can you name every secret that left your laptop today?&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern: the box never dies
&lt;/h2&gt;

&lt;p&gt;Symptom: an old probe URL still answers a week later. You forgot which task created that public endpoint.&lt;/p&gt;

&lt;p&gt;Root cause: the session card never required a teardown step. Free servers still leave traces if you skip cleanup.&lt;/p&gt;

&lt;p&gt;Replacement: teardown equals required, or the checker fails. I write the kill command next to the start command. If I cannot kill it, I do not start it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern: lockfile drift
&lt;/h2&gt;

&lt;p&gt;Symptom: tests pass on the hosted box and fail at home. The import error names a version you do not run.&lt;/p&gt;

&lt;p&gt;Root cause: nobody diffed the box image against the lockfile. A free server image is not your source of truth.&lt;/p&gt;

&lt;p&gt;Replacement: record the lockfile hash in the session card. I compare that hash before I trust a green log. Which hash did your last green run actually use?&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern: live rows in the prompt
&lt;/h2&gt;

&lt;p&gt;Symptom: the prompt includes a real customer email or token. The model reply quotes that row back at you.&lt;/p&gt;

&lt;p&gt;Root cause: you used live data because fake data felt slow. Free model access is still a data exit.&lt;/p&gt;

&lt;p&gt;Replacement: synthetic fixtures only, checked into a tiny folder. I paste a fixture id, never a production record. Would you read that prompt aloud in a team channel?&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern: the scratch host hardens
&lt;/h2&gt;

&lt;p&gt;Symptom: a frontend constant points at the free server host. A teammate clones the repo and hits your old probe.&lt;/p&gt;

&lt;p&gt;Root cause: the demo URL never had an expiry note. Replacement: keep the host in an untracked local file.&lt;/p&gt;

&lt;p&gt;I add that filename to gitignore before the first commit. Did your last commit contain a host you do not own?&lt;/p&gt;

&lt;h2&gt;
  
  
  The four moves I make instead
&lt;/h2&gt;

&lt;p&gt;I want one card, one checker, and one local receipt. The card names the task, the cap, and the deny list. The checker fails closed when a required key is absent.&lt;/p&gt;

&lt;p&gt;The receipt is a local file, not a chat scrollback. This checker is a proposal I have not executed live. Label it as a sample, then run it on a toy tree.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I write the fence card before I touch the prompt box.&lt;/li&gt;
&lt;li&gt;I run the checker and I keep the red output.&lt;/li&gt;
&lt;li&gt;I probe on the free server only after a local accept.&lt;/li&gt;
&lt;li&gt;I tear the box down and I keep the local receipt.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Sample card and checker
&lt;/h2&gt;

&lt;p&gt;The format below is mine, not a vendor schema. Change the keys if you must, but keep the fail-closed rule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# session.fence — proposal format, not a product schema
task_id=probe-184
max_steps=12
max_minutes=25
teardown=required
network=off
lock_hash=unset
deny_glob=.env
deny_glob=*.pem
allowed_path=src/parser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
# Proposal checker. Not executed against a live host.
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;REQUIRED&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;task_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;max_steps&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;max_minutes&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;teardown&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;network&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_card&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;card&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;deny_glob&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;allowed_path&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&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;1&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="nf"&gt;strip&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;line&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="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&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;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;part&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;for&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&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;1&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;key&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;deny_glob&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;allowed_path&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;card&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;card&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--card&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&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;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--root&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&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="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="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;key&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;REQUIRED&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;card&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="n"&gt;key&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;missing&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;fence reject: missing &lt;/span&gt;&lt;span class="sh"&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;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;card&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;teardown&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;required&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;fence reject: teardown must be required&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;card&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;network&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;off&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;fence reject: network must be off for this probe&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&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;steps&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;card&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_steps&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;minutes&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;card&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_minutes&lt;/span&gt;&lt;span class="sh"&gt;'&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;ValueError&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;fence reject: caps must be integers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;40&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;fence reject: caps outside the probe window&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="n"&gt;hits&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;pattern&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;deny_glob&lt;/span&gt;&lt;span class="sh"&gt;'&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="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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;root&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="n"&gt;pattern&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;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&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;hits&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;fence reject: denied files present&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;hit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="n"&gt;receipt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tmp&lt;/span&gt;&lt;span class="sh"&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;fence-receipt.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parents&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;exist_ok&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;task_id={}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;max_steps={}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;lock_hash={}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;task_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;steps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;card&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;lock_hash&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;unset&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="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;fence accept: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;task_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Local policy, not a product promise
&lt;/h3&gt;

&lt;p&gt;The numeric caps in code are my policy, not a vendor limit. I picked a small window so a probe cannot wander all day. You should pick your own window, then keep it fail closed.&lt;/p&gt;

&lt;p&gt;The receipt stores the task id, the step cap, and the lock hash. It does not store prompts, secrets, or customer rows.&lt;/p&gt;

&lt;p&gt;I create the card before I open any chat window. I run the checker from the repo root, not from memory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; scripts tmp
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'tmp/'&lt;/span&gt; &lt;span class="s1"&gt;'session.local.host'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
python3 scripts/fence_check.py &lt;span class="nt"&gt;--card&lt;/span&gt; session.fence &lt;span class="nt"&gt;--root&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nb"&gt;sha256sum &lt;/span&gt;package-lock.json | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I store the receipt under tmp, and I commit nothing there. I delete the scratch host before I close the laptop.&lt;/p&gt;

&lt;p&gt;A reject on missing teardown is a success, not a bug. A reject on a pem file is the whole point. Do not weaken the card to get a green line.&lt;/p&gt;

&lt;h2&gt;
  
  
  A table I use before I upload
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Free server probe&lt;/th&gt;
&lt;th&gt;Laptop only&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Toy bug, synthetic input, teardown written&lt;/td&gt;
&lt;td&gt;Allow&lt;/td&gt;
&lt;td&gt;Also fine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dotenv, pem, or a customer row nearby&lt;/td&gt;
&lt;td&gt;Deny&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need the run tomorrow, or an audit trail&lt;/td&gt;
&lt;td&gt;Deny&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lockfile hash on the host is unknown&lt;/td&gt;
&lt;td&gt;Deny&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Step cap and kill command already written&lt;/td&gt;
&lt;td&gt;Allow&lt;/td&gt;
&lt;td&gt;Also fine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I allow a free server only in the probe column. I deny it for secrets, production data, and long jobs. If a row says deny, I stay on my laptop.&lt;/p&gt;

&lt;p&gt;Why would you rent risk for a task you can run locally?&lt;/p&gt;

&lt;h2&gt;
  
  
  Toy test plan
&lt;/h2&gt;

&lt;p&gt;Seed a toy tree with a fake dotenv file. Expect exit code 3, and expect the path printed.&lt;/p&gt;

&lt;p&gt;Delete the teardown line and expect exit code 2. Set network to on and expect a hard reject.&lt;/p&gt;

&lt;p&gt;Use a clean tree and expect a receipt under tmp. Confirm that receipt path is listed in gitignore.&lt;/p&gt;

&lt;p&gt;I treat a failed check as a blocked session, not a warning. I do not continue in chat while the checker is red. Have you ever talked a red gate into silence?&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits I will not hide
&lt;/h2&gt;

&lt;p&gt;A glob deny list is not a full secret scanner. The checker cannot see steps inside a remote agent. It cannot prove the free server stayed up.&lt;/p&gt;

&lt;p&gt;It cannot prove a vendor quota will remain tomorrow. Hash notes do not replace a real lockfile install. Dotfile matches depend on the Python you actually run.&lt;/p&gt;

&lt;p&gt;This card does not replace diff review or test ownership. I assume a free server can vanish without notice. Keep a laptop copy, or you do not have a copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should skip this lane
&lt;/h2&gt;

&lt;p&gt;Skip it if you need an audit letter for a customer. Skip it if the data is regulated or personal. Skip it if the only copy of the work lives remotely.&lt;/p&gt;

&lt;p&gt;Skip it if you cannot delete the box the same day. A laptop-only repro is slower, and that is fine. I would rather be slow than stuck with a mystery host.&lt;/p&gt;

&lt;h2&gt;
  
  
  One next step
&lt;/h2&gt;

&lt;p&gt;Read current MonkeyCode docs before you trust a free lane. Then run a fence card before the first prompt. I would rather you bounce off the checker than leak a key.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>security</category>
      <category>testing</category>
    </item>
    <item>
      <title>The Scratch Server Went Green. You Still Need a Local Gate.</title>
      <dc:creator>Sam Chen</dc:creator>
      <pubDate>Wed, 23 Sep 2026 16:45:32 +0000</pubDate>
      <link>https://dev.to/codex_1135/the-scratch-server-went-green-you-still-need-a-local-gate-481g</link>
      <guid>https://dev.to/codex_1135/the-scratch-server-went-green-you-still-need-a-local-gate-481g</guid>
      <description>&lt;p&gt;A free agent box is not your merge queue.&lt;br&gt;
You still need a local, boring, repeatable gate.&lt;br&gt;
Would you ship code from a laptop you do not own?&lt;/p&gt;

&lt;p&gt;That is the whole article, stated up front.&lt;br&gt;
The rest is a catalog of ways teams skip it.&lt;br&gt;
I wrote the checks as commands you can run.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why this keeps breaking
&lt;/h2&gt;

&lt;p&gt;Cheap inference changed the cost of a first draft.&lt;br&gt;
It did not change who owns the blast radius.&lt;br&gt;
Did your policy file follow the model, or stay in chat?&lt;/p&gt;

&lt;p&gt;I keep six failures on a short review list.&lt;br&gt;
Each failure looks like speed on a busy afternoon.&lt;br&gt;
Each failure still ships as untracked engineering debt.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 1: Remote green, local skip
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;The scratch server printed a passing test line.&lt;br&gt;
The pull request skips CI because the agent ran tests.&lt;br&gt;
Main now trusts a machine you do not control.&lt;/p&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;People confuse a demo host with a trusted runner.&lt;br&gt;
A free server is a sandbox, not an attesting builder.&lt;br&gt;
Green output is not a signed provenance record.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;Replay every agent patch on your own runner.&lt;br&gt;
Fail the merge when local tests never executed.&lt;br&gt;
Keep the remote box for drafts, not releases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# local_gate.sh — labeled recipe, not a CI platform.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; .agent/origin.json &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"missing .agent/origin.json"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;replayed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.replayed_locally'&lt;/span&gt; .agent/origin.json&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;replayed&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"patch was not replayed locally"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CI&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"refusing to bless a merge outside CI"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;git diff &lt;span class="nt"&gt;--check&lt;/span&gt;
pytest &lt;span class="nt"&gt;-q&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did those agent tests even use your lockfile?&lt;br&gt;
If you cannot answer, you cannot merge yet.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 2: Secrets on a scratch host
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;Someone pasted a dotenv file into the agent thread.&lt;br&gt;
The free server now holds a production token.&lt;br&gt;
Rotation starts only after the screenshot already leaked.&lt;/p&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;Convenience still beats threat modeling under time pressure.&lt;br&gt;
A shared scratch box is not your secret store.&lt;br&gt;
Free compute does not include any free confidentiality.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;Redact first, then inject secrets only in local CI.&lt;br&gt;
I keep a dumb scanner in the pre-push path.&lt;br&gt;
It looks ugly, and it is enough for drafts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# secret_scan.py — labeled example, not a security product.
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;PATTERNS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&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;compile&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;AKIA[0-9A-Z]{16}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;compile&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;-----BEGIN (RSA |OPENSSH )?PRIVATE 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;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(?i)(api[_-]?key|secret)\s*=\s*\S+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&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;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;continue&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;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ignore&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;pat&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;PATTERNS&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;pat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&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="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;secret-like match in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&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;failed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# recipe: scan only staged files&lt;/span&gt;
git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; &lt;span class="nt"&gt;--cached&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; | xargs &lt;span class="nt"&gt;-0&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; python secret_scan.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Would you paste that token into a hallway laptop?&lt;br&gt;
Then do not paste it into a free agent server.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 3: Prompt-as-policy
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;The system prompt says never touch production files.&lt;br&gt;
The agent still edits the deploy workflow anyway.&lt;br&gt;
Nobody encoded the rule as a failing check.&lt;/p&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;Natural language feels like a real control plane.&lt;br&gt;
Models drift over sessions, while files stay put.&lt;br&gt;
A policy that cannot fail CI is a wish.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;Put deny paths in an executable policy file.&lt;br&gt;
I like a tiny allow and deny list in-repo.&lt;br&gt;
The agent may read it, but CI must enforce it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .agent/policy.yml — proposal format&lt;/span&gt;
&lt;span class="na"&gt;deny_globs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.github/workflows/**"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;infra/prod/**"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**/*.env"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**/credentials.json"&lt;/span&gt;
&lt;span class="na"&gt;allow_globs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;src/**"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tests/**"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docs/**"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# policy_check.py — unexecuted example
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fnmatch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;fnmatch&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;

&lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.agent/policy.yml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;diff&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;check_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&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;diff&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;--name-only&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;origin/main...HEAD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;blocked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&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;diff&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;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fnmatch&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;glob&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;glob&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deny_globs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;denied path: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&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;blocked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;blocked&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the check is optional, the anti-pattern remains.&lt;br&gt;
Make it blocking, then argue exceptions in the PR.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 4: Session soup
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;One long agent thread spans three private repositories.&lt;br&gt;
Context from repo A leaks into a patch for B.&lt;br&gt;
The summary sounds confident, but the imports are wrong.&lt;/p&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;Sessions are cheap, and isolation is not the default.&lt;br&gt;
A free server often outlives your actual attention.&lt;br&gt;
Yesterday's stack trace becomes today's invented client API.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;Use one repo, one worktree, and one short session.&lt;br&gt;
Destroy the thread when the branch finally has a name.&lt;br&gt;
Record the origin file before you close the tab.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"generated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-23T12:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"repo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payments-api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"base_sha"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REPLACE_WITH_LOCAL_SHA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scratch-only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"replayed_locally"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set replayed_locally true only after local CI passes.&lt;br&gt;
Anything else is still a draft, not a candidate.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 5: Prod-shaped data on a demo box
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;Someone says the model needs a realistic payload.&lt;br&gt;
A customer export then lands on the scratch server.&lt;br&gt;
The model is free, but that dataset is not.&lt;/p&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;Realism gets confused with actual processing permission here.&lt;br&gt;
Free inference still does not grant data-processing rights.&lt;br&gt;
A redacted fixture is slower, and it is legal.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;Build a tiny fixture set inside the repository.&lt;br&gt;
I would rather ship boring JSON than a real dump.&lt;br&gt;
Name the file fake, and then keep it fake.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ord_test_001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount_cents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lab"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user@example.test"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# recipe: refuse prod-shaped filenames in the diff&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'(customers|pii|prod-dump)'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"prod-shaped filename in the diff"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do you have a deletion ticket for that upload?&lt;br&gt;
If not, you already lost the data conversation.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 6: No local replay of the patch
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Symptom
&lt;/h3&gt;

&lt;p&gt;The patch arrived as a blob from the chat.&lt;br&gt;
Nobody can regenerate it from a recorded prompt.&lt;br&gt;
Reviewers argue with a screenshot, not a command.&lt;/p&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;Generation starts to feel like real authorship too quickly.&lt;br&gt;
Authorship without a replay path is just folklore.&lt;br&gt;
Folklore does not bisect when production later breaks.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;Export a patch file, then apply it locally.&lt;br&gt;
Run the same tests your CI will run tomorrow.&lt;br&gt;
If apply fails, the remote box lied about the tree.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# replay.sh — labeled recipe&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.base_sha'&lt;/span&gt; .agent/origin.json&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
git checkout &lt;span class="nt"&gt;-B&lt;/span&gt; &lt;span class="s2"&gt;"replay/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;base&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
git apply &lt;span class="nt"&gt;--check&lt;/span&gt; /tmp/agent.patch
git apply /tmp/agent.patch

&lt;span class="nv"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
jq &lt;span class="s1"&gt;'.replayed_locally = true'&lt;/span&gt; .agent/origin.json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;tmp&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;tmp&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; .agent/origin.json

pytest &lt;span class="nt"&gt;-q&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can a stranger reproduce this without the original chat?&lt;br&gt;
If they cannot, you do not have a change.&lt;br&gt;
You only have a vibe from a closed tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  A decision table I actually use
&lt;/h2&gt;

&lt;p&gt;I print this table near the merge button.&lt;br&gt;
I still miss a row when the review is rushed.&lt;br&gt;
The table is the review, not the model output.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Merge&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Remote tests only&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Host is not your runner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets in the thread&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Rotate first, then rewrite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Policy only in the prompt&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Encode a failing check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mixed-repo session&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Split and regenerate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real customer payload&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Replace with fixtures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local apply and CI green&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Now it is your patch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where a free model and free server fit
&lt;/h2&gt;

&lt;p&gt;I still want a scratch pad for ugly first drafts.&lt;br&gt;
Throwaway refactors do not deserve a paid cluster.&lt;br&gt;
They also do not deserve a silent path to main.&lt;/p&gt;

&lt;p&gt;MonkeyCode offers free model access and a free server option.&lt;br&gt;
Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;br&gt;
I treat that option as a disposable editor, nothing more.&lt;/p&gt;

&lt;p&gt;The workflow stays boring on purpose for a reason.&lt;br&gt;
Drafts can be remote without becoming merge artifacts.&lt;br&gt;
Merges cannot be remote without a local replay.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Draft on the scratch server with fake fixtures only.&lt;/li&gt;
&lt;li&gt;Export a patch file and a small origin file.&lt;/li&gt;
&lt;li&gt;Scan for secrets, then enforce the deny globs.&lt;/li&gt;
&lt;li&gt;Apply the patch on a clean local worktree.&lt;/li&gt;
&lt;li&gt;Run CI, and only then open the pull request.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Skip step one if your code cannot leave the building.&lt;br&gt;
Skip none of the later steps if it can.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations, loudly
&lt;/h2&gt;

&lt;p&gt;This catalog will not make a model honest.&lt;br&gt;
It only makes a dishonest merge much harder.&lt;br&gt;
That is the actual job of these gates.&lt;/p&gt;

&lt;p&gt;Do not use a free scratch server in these cases.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You handle regulated data, even as supposed context.&lt;/li&gt;
&lt;li&gt;Long-lived credentials still sit in the working tree.&lt;/li&gt;
&lt;li&gt;No local runner matches the production environment closely.&lt;/li&gt;
&lt;li&gt;Your license forbids sending source to another host.&lt;/li&gt;
&lt;li&gt;You cannot explain the change without the chat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also will not claim tokens, uptime, or model names.&lt;br&gt;
Those numbers go stale by the next product page.&lt;br&gt;
Trust the commands you can rerun after lunch.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I do tomorrow morning
&lt;/h2&gt;

&lt;p&gt;Clone the repo, apply the patch, scan, then test.&lt;br&gt;
If a step fails, the agent did not finish.&lt;br&gt;
The chat window is not a merge witness.&lt;/p&gt;

&lt;p&gt;Want a disposable box for that first ugly draft?&lt;br&gt;
Use a free server, then run the local gate at home.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>testing</category>
      <category>git</category>
    </item>
    <item>
      <title>The Tool Returned 200. You Still Have No Allowlist.</title>
      <dc:creator>Sam Chen</dc:creator>
      <pubDate>Tue, 22 Sep 2026 15:02:25 +0000</pubDate>
      <link>https://dev.to/codex_1135/the-tool-returned-200-you-still-have-no-allowlist-3ek3</link>
      <guid>https://dev.to/codex_1135/the-tool-returned-200-you-still-have-no-allowlist-3ek3</guid>
      <description>&lt;p&gt;A successful tool call is not a reviewed change.&lt;br&gt;
Cheap inference only makes those side effects cheaper.&lt;br&gt;
Do you gate the tool, or trust the chat?&lt;/p&gt;

&lt;p&gt;I keep watching agents celebrate a raw HTTP 200.&lt;br&gt;
The demo parses JSON and calls the job done.&lt;br&gt;
Nobody asks which tools were even legal.&lt;/p&gt;

&lt;p&gt;That missing allowlist is the whole incident.&lt;br&gt;
The model proposed a call. The runner obeyed it.&lt;br&gt;
Git never saw a policy, only a mutated tree.&lt;/p&gt;
&lt;h2&gt;
  
  
  The problem is not the model
&lt;/h2&gt;

&lt;p&gt;Tool calling is loud in developer feeds this month.&lt;br&gt;
People wire HTTP, shell, and files into one loop.&lt;br&gt;
Then a green status gets treated like a contract.&lt;/p&gt;

&lt;p&gt;A green status code is not a contract.&lt;br&gt;
It is an unscoped side effect with a nickname.&lt;br&gt;
Who signed the allowlist for that &lt;code&gt;curl&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;I do not care that inference was complimentary.&lt;br&gt;
I do not care that the box had no invoice.&lt;br&gt;
I care that the tool could mutate production.&lt;/p&gt;
&lt;h2&gt;
  
  
  How I read a tool-calling failure
&lt;/h2&gt;

&lt;p&gt;I start from symptoms, not from the prompt text.&lt;br&gt;
The chat window is a story about intent.&lt;br&gt;
The transcript is the only evidence I trust.&lt;/p&gt;

&lt;p&gt;If there is no transcript, I already lost.&lt;br&gt;
I cannot replay the call, only the narrative.&lt;br&gt;
Would you debug production from a Slack summary?&lt;/p&gt;

&lt;p&gt;Here is the catalog I actually use on reviews.&lt;br&gt;
Each entry has a symptom, a cause, a replacement.&lt;br&gt;
Steal the replacement. Leave the branding on the floor.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 1: The open tool belt
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom.&lt;/strong&gt; The agent can hit HTTP, shell, SQL, files.&lt;br&gt;
One schema dump lists twenty tools with vague copy.&lt;br&gt;
A "search" tool can POST, and nobody noticed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause.&lt;/strong&gt; More tools feel more autonomous in demos.&lt;br&gt;
Teams drop a kitchen-sink pack into every task.&lt;br&gt;
Default allow becomes the real architecture, quietly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replacement.&lt;/strong&gt; Default deny. One task, very few tools.&lt;br&gt;
Name every tool in a committed allowlist file.&lt;br&gt;
If the task changes, the allowlist changes in git.&lt;/p&gt;

&lt;p&gt;Would you hand that belt to a weekend contractor?&lt;br&gt;
If the answer is no, stop handing it to a model.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 2: Secrets ride in the prompt
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom.&lt;/strong&gt; Tool args contain tokens, cookies, or PEM blobs.&lt;br&gt;
Those args went out with the remote model context.&lt;br&gt;
Your so-called local agent was never actually local.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause.&lt;/strong&gt; Pasting the key into the prompt is easy.&lt;br&gt;
A complimentary endpoint feels disposable, so people slacken.&lt;br&gt;
Disposable compute is still somebody else's machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replacement.&lt;/strong&gt; The runner injects secrets at call time.&lt;br&gt;
The model sees a handle like &lt;code&gt;secret:billing_read&lt;/code&gt;.&lt;br&gt;
The gate strips anything that looks like a credential.&lt;/p&gt;

&lt;p&gt;If the model can print the key, design already failed.&lt;br&gt;
Rotate that key. Then fix the proposal path.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 3: Stdout is treated as truth
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom.&lt;/strong&gt; The agent reads stdout and patches source.&lt;br&gt;
A truncated JSON payload still "looks right enough."&lt;br&gt;
Hallucinated fields survive because nobody schema-checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause.&lt;/strong&gt; Models trust the last tokens they saw.&lt;br&gt;
Tool output is just more tokens in the window.&lt;br&gt;
There is no type boundary after the 200.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replacement.&lt;/strong&gt; Every tool returns a typed envelope.&lt;br&gt;
Validate with JSON Schema before the model sees it.&lt;br&gt;
Reject extra fields. Reject missing required keys.&lt;/p&gt;

&lt;p&gt;A 200 with a bad body is still a failed call.&lt;br&gt;
Do not let the agent launder that into a diff.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 4: No first-class transcript
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom.&lt;/strong&gt; Slack has a summary. Disk has a mutation.&lt;br&gt;
You cannot replay which tool ran, with which args.&lt;br&gt;
The chat closed. The side effect stayed on disk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause.&lt;/strong&gt; People log model prose, not tool IO.&lt;br&gt;
Demos optimize for narrative, not for later forensics.&lt;br&gt;
Forensics needs bytes, hashes, and honest timestamps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replacement.&lt;/strong&gt; Write JSONL for every gated tool call.&lt;br&gt;
Store tool name, arg hash, result hash, and decision.&lt;br&gt;
CI fails if a call happened outside the gate.&lt;/p&gt;

&lt;p&gt;Can you rebuild the incident without the chat UI?&lt;br&gt;
If you cannot, you shipped a ghost.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 5: The free box is "localhost"
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Symptom.&lt;/strong&gt; The agent workspace is a complimentary server.&lt;br&gt;
Your checkout, your &lt;code&gt;.env&lt;/code&gt;, and your SSH agent followed.&lt;br&gt;
Egress is wide open "so the tools can work."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause.&lt;/strong&gt; Free compute feels like an extra laptop.&lt;br&gt;
People confuse the invoice with the trust boundary.&lt;br&gt;
A free host is still a different machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replacement.&lt;/strong&gt; Use an ephemeral workspace. No prod credentials.&lt;br&gt;
Pin egress to the allowlisted hosts, nothing else.&lt;br&gt;
Destroy the box after the transcript is copied off.&lt;/p&gt;

&lt;p&gt;Would you scp your wallet onto a shared runner?&lt;br&gt;
Then stop treating the free box as home.&lt;/p&gt;
&lt;h2&gt;
  
  
  The replacement pattern, in one pipeline
&lt;/h2&gt;

&lt;p&gt;I want three files before any agent tool may run.&lt;br&gt;
An allowlist. A schema. A transcript path.&lt;br&gt;
No file, no call. That is the whole policy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tools/
  allowlist.yml
  schemas/http_get.json
  schemas/repo_read.json
  transcripts/.gitkeep
scripts/
  tool_gate.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gate sits between the model and the real tool.&lt;br&gt;
The model proposes. The gate decides. The OS executes.&lt;br&gt;
If that order flips, you are already in production.&lt;/p&gt;
&lt;h2&gt;
  
  
  Artifact: a local tool gate you can run
&lt;/h2&gt;

&lt;p&gt;This is a proposed runner, not a production IAM system.&lt;br&gt;
I am not claiming latency numbers or pass rates.&lt;br&gt;
Copy it, break it, then tighten the schemas.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;tools/allowlist.yml&lt;/code&gt;
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Proposed policy. Commit this beside the task, not in chat.&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;read-only&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;docs&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;probe"&lt;/span&gt;
&lt;span class="na"&gt;max_calls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;
&lt;span class="na"&gt;max_arg_bytes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4096&lt;/span&gt;
&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deny&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;repo_read&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;schemas/repo_read.json&lt;/span&gt;
    &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http_get&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;schemas/http_get.json&lt;/span&gt;
    &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;api.github.com&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;github_readonly&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;tools/schemas/http_get.json&lt;/code&gt;
&lt;/h3&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="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://json-schema.org/draft/2020-12/schema"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"additionalProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^https://api&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.github&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.com/"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timeout_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"minimum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"maximum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;scripts/tool_gate.py&lt;/code&gt;
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Proposed local tool gate. Not IAM. Run it yourself.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlparse&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;jsonschema&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Draft202012Validator&lt;/span&gt;

&lt;span class="n"&gt;SECRET_RE&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;compile&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;(?i)(api[_-]?key|secret|token|bearer\s+[A-Za-z0-9\-._]+|-----BEGIN )&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sha256_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&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;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&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;looks_like_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SECRET_RE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&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;decide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allowlist&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="n"&gt;proposal&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="n"&gt;schema_root&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;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proposal&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;tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proposal&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;args&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;raw_args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&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="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;record&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;ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arg_sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sha256_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_args&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&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;deny&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;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="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;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&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;missing tool name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;record&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;raw_args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;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;allowlist&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;max_arg_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&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;args exceed max_arg_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;looks_like_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&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;secret-shaped value in args&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;
    &lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&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;allowlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="ow"&gt;is&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;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&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;tool not in allowlist&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;
    &lt;span class="n"&gt;schema_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;schema_root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;schema&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Draft202012Validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;iter_errors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&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;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;list&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;path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&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;schema: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;errors&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="n"&gt;message&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;spec&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;network&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&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;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;urlparse&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;hostname&lt;/span&gt; &lt;span class="ow"&gt;or&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;host&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="n"&gt;spec&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;hosts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
            &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&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;host not pinned&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&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;allow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&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;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;append_transcript&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;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&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="o"&gt;-&amp;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;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parents&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;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&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="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Gate one proposed tool call.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--allowlist&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&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;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--transcript&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&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;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--proposal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&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;ns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;allowlist_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;allowlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allowlist_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&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;transcript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;&amp;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;allowlist&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;max_calls&lt;/span&gt;&lt;span class="sh"&gt;"&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;record&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;ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arg_sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&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;deny&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;reason&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;max_calls exceeded&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_transcript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="n"&gt;proposal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allowlist_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;append_transcript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&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;allow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Install the two libraries the gate actually imports.&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 &lt;span class="nb"&gt;install &lt;/span&gt;pyyaml jsonschema
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now throw a bad host at it on purpose.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 scripts/tool_gate.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allowlist&lt;/span&gt; tools/allowlist.yml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transcript&lt;/span&gt; tools/transcripts/run.jsonl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--proposal&lt;/span&gt; &lt;span class="s1"&gt;'{"tool":"http_get","args":{"url":"https://evil.example/steal"}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see &lt;code&gt;"decision": "deny"&lt;/code&gt; and &lt;code&gt;host not pinned&lt;/code&gt;.&lt;br&gt;
That deny is the feature. Keep the JSONL line.&lt;/p&gt;

&lt;p&gt;Now throw a pinned host at the same gate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 scripts/tool_gate.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allowlist&lt;/span&gt; tools/allowlist.yml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transcript&lt;/span&gt; tools/transcripts/run.jsonl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--proposal&lt;/span&gt; &lt;span class="s1"&gt;'{"tool":"http_get","args":{"url":"https://api.github.com/repos/octocat/Hello-World"}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see &lt;code&gt;"decision": "allow"&lt;/code&gt; and a new arg hash.&lt;br&gt;
Still do not execute the real HTTP call from this script.&lt;br&gt;
The gate only judges the proposal. Execution stays elsewhere.&lt;/p&gt;

&lt;p&gt;Secret-shaped args should die before schema even runs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 scripts/tool_gate.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allowlist&lt;/span&gt; tools/allowlist.yml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transcript&lt;/span&gt; tools/transcripts/run.jsonl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--proposal&lt;/span&gt; &lt;span class="s1"&gt;'{"tool":"http_get","args":{"url":"https://api.github.com/?token=ghp_example"}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expect deny. Expect &lt;code&gt;secret-shaped value in args&lt;/code&gt;.&lt;br&gt;
If that call had reached a remote model, you already leaked.&lt;/p&gt;
&lt;h2&gt;
  
  
  Decision table I keep next to the gate
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tool missing from allowlist&lt;/td&gt;
&lt;td&gt;Open belt&lt;/td&gt;
&lt;td&gt;Deny, fail the job&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Args fail JSON Schema&lt;/td&gt;
&lt;td&gt;Model improvised&lt;/td&gt;
&lt;td&gt;Deny, keep transcript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secret-shaped string in args&lt;/td&gt;
&lt;td&gt;Prompt leak&lt;/td&gt;
&lt;td&gt;Deny, redact the log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Host not in the pin list&lt;/td&gt;
&lt;td&gt;Egress drift&lt;/td&gt;
&lt;td&gt;Deny&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Call count exceeds &lt;code&gt;max_calls&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Runaway loop&lt;/td&gt;
&lt;td&gt;Deny, kill the runner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP 200 with invalid body&lt;/td&gt;
&lt;td&gt;Fake success&lt;/td&gt;
&lt;td&gt;Treat as an error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transcript file missing&lt;/td&gt;
&lt;td&gt;Blind run&lt;/td&gt;
&lt;td&gt;Do not merge&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the table before you read the model recap.&lt;br&gt;
The recap will sound confident. The table will not lie.&lt;br&gt;
Which column are you actually merging?&lt;/p&gt;
&lt;h2&gt;
  
  
  A debugging workflow I follow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Reproduce the proposal as JSON, never as chat paste.&lt;/li&gt;
&lt;li&gt;Run that JSON through &lt;code&gt;tool_gate.py&lt;/code&gt; with the task allowlist.&lt;/li&gt;
&lt;li&gt;Check deny or allow against the table above.&lt;/li&gt;
&lt;li&gt;Execute the real tool only in a throwaway workspace.&lt;/li&gt;
&lt;li&gt;Diff the transcript against git, not against memory.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If step one is hard, the agent UI is hiding the call.&lt;br&gt;
Fix the UI. Do not "just ask it again."&lt;br&gt;
A hidden call is an ungated call.&lt;/p&gt;

&lt;p&gt;I also grep the transcript before I open the diff.&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="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import json, pathlib, sys
p = pathlib.Path("tools/transcripts/run.jsonl")
if not p.exists():
    print("no transcript"); sys.exit(2)
rows = [json.loads(line) for line in p.read_text().splitlines() if line]
denies = [r for r in rows if r.get("decision") == "deny"]
print(f"calls={len(rows)} denies={len(denies)}")
for r in denies:
    print(r["reason"])
sys.exit(1 if denies else 0)
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero denies does not mean the change is good.&lt;br&gt;
It only means the gate stayed on during the run.&lt;br&gt;
You still review the code like a human wrote it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a cheap model loop actually helps
&lt;/h2&gt;

&lt;p&gt;You still need a disposable place to practice dry-runs.&lt;br&gt;
Not to touch production. To generate illegal proposals.&lt;br&gt;
The gate gets stronger when you feed it bad calls.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;MonkeyCode has free model access and a free server option.&lt;br&gt;
I treat that pair as a sandbox for proposal generation only.&lt;br&gt;
The allowlist, schemas, and gate stay in the application repo.&lt;br&gt;
If you try the sandbox, keep customer data and prod keys off it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations, stated plainly
&lt;/h2&gt;

&lt;p&gt;This gate is not IAM, and it is not a TEE.&lt;br&gt;
It will not save you from a malicious runner image.&lt;br&gt;
It will not sign an audit log your regulator accepts.&lt;/p&gt;

&lt;p&gt;Do not use this approach when any of these apply:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You handle regulated personal data in tool args&lt;/li&gt;
&lt;li&gt;The tool can move money or mutate cloud IAM&lt;/li&gt;
&lt;li&gt;You cannot destroy the workspace after the run&lt;/li&gt;
&lt;li&gt;You need a vendor-backed, signed audit trail&lt;/li&gt;
&lt;li&gt;The provider cannot see even redacted proposals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Complimentary model access can change without notice.&lt;br&gt;
A free server is not your compliance program.&lt;br&gt;
Price zero is not the same as risk zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should skip this
&lt;/h2&gt;

&lt;p&gt;Skip it if your agents never call tools at all.&lt;br&gt;
Skip it if a human already types every request.&lt;br&gt;
Skip it if security already brokers every egress path.&lt;/p&gt;

&lt;p&gt;This catalog is for teams wiring agents to real APIs.&lt;br&gt;
If that is not you, keep the tools in a drawer.&lt;br&gt;
You do not need a gate for a function you never invoke.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I will not merge
&lt;/h2&gt;

&lt;p&gt;I will not merge a diff that needed a mystery tool call.&lt;br&gt;
I will not merge "the agent said the API looked fine."&lt;br&gt;
I will not merge a workspace that still holds prod secrets.&lt;/p&gt;

&lt;p&gt;Show me the allowlist.&lt;br&gt;
Show me the transcript.&lt;br&gt;
Show me the deny that proved the gate was on.&lt;/p&gt;

&lt;p&gt;If those three files are missing, the 200 never happened.&lt;br&gt;
Not in a way I can defend later.&lt;br&gt;
And later is when this stuff actually matters.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>programming</category>
      <category>testing</category>
    </item>
    <item>
      <title>The Agent Diff Has No Parent SHA. Stop Merging It.</title>
      <dc:creator>Sam Chen</dc:creator>
      <pubDate>Mon, 21 Sep 2026 13:14:56 +0000</pubDate>
      <link>https://dev.to/codex_1135/the-agent-diff-has-no-parent-sha-stop-merging-it-14pb</link>
      <guid>https://dev.to/codex_1135/the-agent-diff-has-no-parent-sha-stop-merging-it-14pb</guid>
      <description>&lt;p&gt;A parentless agent patch is not a change. It is a rumor about some other tree. I will not merge that rumor today.&lt;/p&gt;

&lt;p&gt;Did the model even start from your current HEAD?&lt;/p&gt;

&lt;h2&gt;
  
  
  Parent SHA first
&lt;/h2&gt;

&lt;p&gt;I review AI patches by parent SHA first. I read the patched code only after that. Most failures hide between two commits, not inside one file.&lt;/p&gt;

&lt;p&gt;The agent branched from an unnamed commit. Your teammates pushed new commits onto main. And nobody recorded the original base SHA.&lt;/p&gt;

&lt;p&gt;Now the diff applies with hunks of fiction. Does this sequence already sound familiar to you?&lt;/p&gt;

&lt;h2&gt;
  
  
  How to read this catalog
&lt;/h2&gt;

&lt;p&gt;Each anti-pattern below has three named parts. I list symptoms, root cause, and a runnable replacement. I keep every replacement small on purpose.&lt;/p&gt;

&lt;p&gt;You should keep those replacements small too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 1: The orphan patch
&lt;/h2&gt;

&lt;p&gt;The pull request shows a diff without a parent. &lt;code&gt;git apply&lt;/code&gt; works Monday and then fails Tuesday. The chat says done and dumps files, not commits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The pull request never names a recorded &lt;code&gt;base_sha&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The same patch fails after an unrelated main push.&lt;/li&gt;
&lt;li&gt;The agent returns file blobs instead of commits.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;The agent never serialized its real starting tree. You treated the chat blob as history. Git still disagrees with that entire chat story.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;Record the parent SHA before the first prompt. Refuse any patch that omits that SHA.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# example: persist the only parent that matters&lt;/span&gt;
git rev-parse HEAD &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .agent-base-sha
git status &lt;span class="nt"&gt;--porcelain&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .agent-dirty-index
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Would you accept a human PR with no merge base?&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 2: The dirty overlay
&lt;/h2&gt;

&lt;p&gt;Local edits sit unstaged while the agent writes files. The same path exists in your tree and the patch. Tests pass because your leftovers filled the holes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Unstaged local files share paths with the agent patch.&lt;/li&gt;
&lt;li&gt;Tests pass only with your leftover working tree.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git status&lt;/code&gt; was never clean at agent start.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;Two authors shared one working tree by accident. Neither person truly owned that shared tree. The agent overlayed a dirty index without checking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;Start the agent on a clean worktree. Use a second clone if the tree is busy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# example: refuse to start on a dirty tree&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git status &lt;span class="nt"&gt;--porcelain&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"dirty tree; refuse agent start"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is that extra clone disposable on purpose then? Keep that clone disposable after every agent run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 3: The silent rebase
&lt;/h2&gt;

&lt;p&gt;The patch applies with fuzz and with offset. Reviewers read a diff that HEAD will never see. CI on main fails after a "successful" agent run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git apply&lt;/code&gt; reports offsets you did not review.&lt;/li&gt;
&lt;li&gt;The reviewed hunks never match &lt;code&gt;git show HEAD&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Main CI fails after the agent session looked green.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;HEAD moved while the agent was writing. Nobody replayed the patch onto current main. You reviewed a ghost of yesterday's tree.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;Rebase the agent commit onto current HEAD. Review that replayed diff with fresh eyes. Ship only that replayed commit after review.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# example: replay onto today's origin/main&lt;/span&gt;
&lt;span class="nv"&gt;BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; .agent-base-sha&lt;span class="si"&gt;)&lt;/span&gt;
git fetch origin
git rebase &lt;span class="nt"&gt;--onto&lt;/span&gt; origin/main &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the rebase conflicts, the original review is void. Run the review again on the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 4: Generated-file amnesia
&lt;/h2&gt;

&lt;p&gt;The source changed and the lockfile stayed put. Generated protobuf stubs lag behind the &lt;code&gt;.proto&lt;/code&gt; edit. CI generates files the agent never opened.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Source imports a package the lockfile does not pin.&lt;/li&gt;
&lt;li&gt;Build artifacts differ between agent host and CI.&lt;/li&gt;
&lt;li&gt;Reviewers never opened the generated file diff.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;The agent patched only the file it was shown. The build graph stayed invisible to the session. You merged half of a real change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;List generated outputs inside the receipt file. Diff those outputs after the local replay.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# example receipt fragment, not a live config
must_regenerate:
  - package-lock.json
  - src/generated/
commands:
  - npm install --package-lock-only
  - make proto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did you read the generated diff at all? If not, you skipped the real change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 5: Remote-only green
&lt;/h2&gt;

&lt;p&gt;The agent host printed a cheerful all-tests-passed line. Your laptop cannot even find that command. No artifact ever left the remote session.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Success exists only as a sentence in chat.&lt;/li&gt;
&lt;li&gt;The command line is not checked into the receipt.&lt;/li&gt;
&lt;li&gt;Local replay is skipped because the host looked green.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;That success lived on another machine entirely. You imported the story, not the bytes. A free remote host makes that swap easy.&lt;/p&gt;

&lt;p&gt;I sometimes point the first pass at MonkeyCode. Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;MonkeyCode offers free model access and a free server option. That host is fine for a first pass. It is not my merge oracle though.&lt;/p&gt;

&lt;p&gt;Copy the command list off the host. Run every command on your local checkout. Store the exit codes beside both SHAs.&lt;/p&gt;

&lt;p&gt;A green remote log without a local receipt is theater. Do not import that theater into main.&lt;/p&gt;

&lt;h2&gt;
  
  
  The artifact: a two-SHA receipt
&lt;/h2&gt;

&lt;p&gt;I want two SHAs sitting on disk. I want the command transcript beside them.&lt;/p&gt;

&lt;p&gt;Here is an example script for the receipt. Treat it as a proposal, not a benchmark.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Example only: write a two-SHA agent receipt.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;proc&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;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&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;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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;proc&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;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&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;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;2&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;usage: receipt.py write|verify&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

    &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.agent-receipt.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;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&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;rev-parse&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;HEAD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&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;rev-parse&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;--abbrev-ref&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;HEAD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;dirty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&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;status&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;--porcelain&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;mode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;write&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;payload&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;recorded_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base_sha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;branch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dirty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dirty&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dirty_paths&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dirty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result_sha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;commands&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="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&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="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wrote base_sha=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;head&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;return&lt;/span&gt; &lt;span class="mi"&gt;0&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;dirty&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verify&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base_sha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;merge_base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&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;merge-base&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;merge_base&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;base&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="s"&gt;base &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is not an ancestor of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;head&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;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&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;dirty&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;receipt started dirty; refuse verify&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result_sha&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;head&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verified_at&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;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&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="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok base=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; result=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;head&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;return&lt;/span&gt; &lt;span class="mi"&gt;0&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;unknown mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How I run the example
&lt;/h3&gt;

&lt;p&gt;This run sequence is a labeled example. Change the names to match your repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. freeze the parent on a clean tree&lt;/span&gt;
python3 receipt.py write &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1

&lt;span class="c"&gt;# 2. let the agent work, then commit&lt;/span&gt;
&lt;span class="c"&gt;# git add -A &amp;amp;&amp;amp; git commit -m "agent: named change"&lt;/span&gt;

&lt;span class="c"&gt;# 3. prove the parent still ancestors HEAD&lt;/span&gt;
python3 receipt.py verify &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1

&lt;span class="c"&gt;# 4. rebase if origin/main moved under you&lt;/span&gt;
git fetch origin
git rebase origin/main
python3 receipt.py verify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No receipt means no review from me. That is the whole review rule here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test plan for the receipt
&lt;/h2&gt;

&lt;p&gt;Do not trust this example script blindly. Break it on purpose before any agent run.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;write&lt;/code&gt; on a dirty tree and expect exit &lt;code&gt;1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Commit a dummy file, then run &lt;code&gt;verify&lt;/code&gt; for exit &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Reset &lt;code&gt;--hard&lt;/code&gt; to &lt;code&gt;HEAD~1&lt;/code&gt; and expect &lt;code&gt;verify&lt;/code&gt; to fail.&lt;/li&gt;
&lt;li&gt;Rebase onto an unrelated commit and expect ancestor failure.&lt;/li&gt;
&lt;li&gt;Delete &lt;code&gt;.agent-receipt.json&lt;/code&gt; and confirm the review stops.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If step 3 still prints &lt;code&gt;ok&lt;/code&gt;, the script is lying. Fix it before any real agent run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision table
&lt;/h2&gt;

&lt;p&gt;Use this table during review, not during prompting. When a row says no, stop merging.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Trust the patch?&lt;/th&gt;
&lt;th&gt;Next move&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No &lt;code&gt;base_sha&lt;/code&gt; on disk&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Restart the agent on a named SHA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dirty tree at &lt;code&gt;write&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Clean clone, then run &lt;code&gt;write&lt;/code&gt; again&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;merge-base&lt;/code&gt; is not &lt;code&gt;base_sha&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Rebase, then re-review the new diff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remote tests green only&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Replay the same commands locally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generated files missing from the diff&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Run generators, then diff outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two SHAs plus a clean local replay&lt;/td&gt;
&lt;td&gt;Candidate only&lt;/td&gt;
&lt;td&gt;Human review of the replayed diff&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice the last row still says only candidate. People still need to read the replayed diff.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does not catch
&lt;/h2&gt;

&lt;p&gt;A parent SHA is not a behavior specification. It does not prove runtime behavior by itself. The example script ignores several real failure classes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flaky tests that passed once on the remote host.&lt;/li&gt;
&lt;li&gt;Secrets that existed only in the remote environment.&lt;/li&gt;
&lt;li&gt;Prompt text that never entered the git history.&lt;/li&gt;
&lt;li&gt;Semantic bugs that still compile and link cleanly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I still read the replayed diff slowly. Do you actually read that replayed diff?&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this
&lt;/h2&gt;

&lt;p&gt;Skip this workflow if you have no local clone. Skip it if hermetic CI is already your only trusted runner.&lt;/p&gt;

&lt;p&gt;Skip it for throwaway spikes you will delete before lunch. Also skip it if you cannot run the project's tests locally.&lt;/p&gt;

&lt;p&gt;A receipt without a replay is another pretty story. Remote-only teams need a named CI SHA.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;The model will keep returning patches anyway today. HEAD will keep moving under those patches. Those two facts do not care about your deadline.&lt;/p&gt;

&lt;p&gt;I want a parent SHA, a result SHA, and a local replay. Everything else is narration from a model.&lt;/p&gt;

&lt;p&gt;If you draft on a free remote host, steal this receipt script. Make the host prove its parent SHA. Then decide whether the replayed diff is mergeable.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>git</category>
      <category>testing</category>
      <category>programming</category>
    </item>
    <item>
      <title>You Closed the Chat. Five Anti-Patterns Still Merged.</title>
      <dc:creator>Sam Chen</dc:creator>
      <pubDate>Sun, 20 Sep 2026 11:21:13 +0000</pubDate>
      <link>https://dev.to/codex_1135/you-closed-the-chat-five-anti-patterns-still-merged-4pm1</link>
      <guid>https://dev.to/codex_1135/you-closed-the-chat-five-anti-patterns-still-merged-4pm1</guid>
      <description>&lt;p&gt;The chat went quiet, so the work felt finished. That feeling is the bug, not the model.&lt;/p&gt;

&lt;p&gt;I keep a five-row anti-pattern catalog for this moment. Each row lists symptoms, root cause, and a local replacement.&lt;/p&gt;

&lt;p&gt;A free session can draft code without any shame. Your repo still owns the merge gate, always.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this catalog exists
&lt;/h2&gt;

&lt;p&gt;AI coding sessions now end in minutes, not days. Speed is not the same thing as a merge contract.&lt;/p&gt;

&lt;p&gt;Have you ever pasted a green snippet as review? I have done that, and the next outage was local.&lt;/p&gt;

&lt;p&gt;Trending posts ask if models outgrew our tests. Wrong question for this catalog on your machine. Those tests never ran against your actual checkout.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to read each row
&lt;/h2&gt;

&lt;p&gt;Use the catalog during the last ten minutes. Do not use it as a vibe check.&lt;/p&gt;

&lt;p&gt;For every row I want three things only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Symptoms you can spot in the diff or logs&lt;/li&gt;
&lt;li&gt;Root cause stated in one blunt sentence&lt;/li&gt;
&lt;li&gt;A replacement pattern with a real command&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No score. No leaderboard. Just fail-closed checks you can rerun.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 1: Clipboard as CI
&lt;/h2&gt;

&lt;p&gt;You copy the patch into the tree. You never run a command that can fail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The PR quotes the chat, not a failing command&lt;/li&gt;
&lt;li&gt;Generator output never touches a &lt;code&gt;scripts/&lt;/code&gt; gate&lt;/li&gt;
&lt;li&gt;Review says "looks good" with zero pasted output&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;The session produced text. You treated that text as an execution receipt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement: a local fail-closed gate
&lt;/h3&gt;

&lt;p&gt;Write one script the model cannot mark green by talking.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# scripts/local-gate.sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/.."&lt;/span&gt;

&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; reproduce.sh
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; decisions.md

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; rg &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  if &lt;/span&gt;rg &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"AKIA|BEGIN PRIVATE|api_key&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s2"&gt;*="&lt;/span&gt; &lt;span class="nt"&gt;--glob&lt;/span&gt; &lt;span class="s1"&gt;'!decisions.md'&lt;/span&gt; .&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"secret-shaped text in the tree"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
  &lt;span class="k"&gt;fi
fi&lt;/span&gt;

./reproduce.sh
python3 scripts/check_defaults.py
pytest &lt;span class="nt"&gt;-q&lt;/span&gt; tests/gate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did the chat approve the patch in prose? Fine. Did &lt;code&gt;local-gate.sh&lt;/code&gt; exit zero on your laptop?&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 2: Anonymous runtime
&lt;/h2&gt;

&lt;p&gt;You ran the demo on a borrowed box. Nobody on the team can replay it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The README says it worked on "the server"&lt;/li&gt;
&lt;li&gt;No commit SHA, seed, or interpreter version is recorded&lt;/li&gt;
&lt;li&gt;A teammate cannot hit the same path on a laptop&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;A shared runtime became the source of truth. Your laptop never signed the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement: a reproduce file that pins the command
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# reproduce.sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"git=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse HEAD&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"python=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;python3 &lt;span class="nt"&gt;--version&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="c"&gt;# shellcheck disable=SC1091&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements-gate.txt
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; app.probe &lt;span class="nt"&gt;--fixture&lt;/span&gt; tests/fixtures/min.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The box can be disposable and even free. The script cannot vanish with the session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 3: Model-chosen defaults
&lt;/h2&gt;

&lt;p&gt;The generator picked timeouts, pool sizes, and log levels. You shipped those numbers unchanged.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;timeout=30&lt;/code&gt;, &lt;code&gt;retries=3&lt;/code&gt;, &lt;code&gt;workers=4&lt;/code&gt; with no comment&lt;/li&gt;
&lt;li&gt;Config keys appear only inside generated files&lt;/li&gt;
&lt;li&gt;No test asserts a deadline or a fail-closed limit&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;Defaults look like expertise in a hurry. They are the model's prior, frozen into your repo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement: an explicit defaults review
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# scripts/check_defaults.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;ALLOWED&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;timeout_s&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="mi"&gt;5&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="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retries&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workers&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="mi"&gt;1&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="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="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app/config.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="k"&gt;for&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;allowed&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ALLOWED&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;match&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;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;rf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\s*=\s*(\d+)&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="ow"&gt;or&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;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&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="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;allowed&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="s"&gt;unreviewed default: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&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;fail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;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;defaults reviewed&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;Would you defend &lt;code&gt;retries=3&lt;/code&gt; in an incident channel? If not, do not merge that line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 4: Secrets in the prompt
&lt;/h2&gt;

&lt;p&gt;You pasted &lt;code&gt;.env&lt;/code&gt; so the model could "see the real shape." That thread now outlives the key rotation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Chat history contains hostnames, tokens, or customer ids&lt;/li&gt;
&lt;li&gt;Generated fixtures still point at production URLs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.gitignore&lt;/code&gt; looks clean, but the session log is not&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;Context dumping felt faster than making a fake fixture. The leak is now immortal in the thread.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement: a fixture that cannot authenticate
&lt;/h3&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="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"base_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://127.0.0.1:9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test-token-not-secret"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fixture-user"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the model needs shape, give it a dead socket. Never give it a live key.&lt;/p&gt;

&lt;p&gt;Scan the tree before the gate returns green:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# last line of local-gate.sh before pytest&lt;/span&gt;
python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
from pathlib import Path
bad = ("AKIA", "BEGIN PRIVATE", "sk-live", "xoxb-")
root = Path(".")
for path in root.rglob("*"):
    if path.suffix in {".png", ".pyc"} or ".venv" in path.parts:
        continue
    try:
        text = path.read_text(encoding="utf-8", errors="ignore")
    except OSError:
        continue
    if any(token in text for token in bad):
        raise SystemExit(f"secret-shaped text: {path}")
print("no secret-shaped text")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did the fixture still need a routable host? Then it is not a fixture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-pattern 5: Forty-turn design doc
&lt;/h2&gt;

&lt;p&gt;The architecture lives in a scrolling chat. The repo only has a shrug.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The PR links a transcript instead of a table&lt;/li&gt;
&lt;li&gt;Two engineers recount different "final" decisions&lt;/li&gt;
&lt;li&gt;Re-asking the model quietly changes the story&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;Conversation is cheap and feels like design. A twelve-line table is the actual design.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacement: a decision table in the repo
&lt;/h3&gt;

&lt;p&gt;Keep &lt;code&gt;decisions.md&lt;/code&gt; tiny. Blank cells mean the chat is not done.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;th&gt;Options&lt;/th&gt;
&lt;th&gt;Chosen&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;th&gt;Revisit when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;session cookie / static token / none&lt;/td&gt;
&lt;td&gt;static token in tests&lt;/td&gt;
&lt;td&gt;local gate must not hit IdP&lt;/td&gt;
&lt;td&gt;we add a real user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;laptop / borrowed server / CI&lt;/td&gt;
&lt;td&gt;laptop owns merge&lt;/td&gt;
&lt;td&gt;borrowed boxes lie about paths&lt;/td&gt;
&lt;td&gt;CI is green twice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timeouts&lt;/td&gt;
&lt;td&gt;5s / 30s / none&lt;/td&gt;
&lt;td&gt;5s&lt;/td&gt;
&lt;td&gt;fail closed on hang&lt;/td&gt;
&lt;td&gt;p99 is measured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retries&lt;/td&gt;
&lt;td&gt;0 / 3 / infinite&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;retries hide probe bugs&lt;/td&gt;
&lt;td&gt;idempotency is proven&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixtures&lt;/td&gt;
&lt;td&gt;prod dump / synthetic&lt;/td&gt;
&lt;td&gt;synthetic&lt;/td&gt;
&lt;td&gt;secrets stay out of prompts&lt;/td&gt;
&lt;td&gt;schema changes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Print this table in the PR body. If a cell is blank, stop generating more code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The artifact: one gate, one table
&lt;/h2&gt;

&lt;p&gt;Here is the workflow I want on the last commit. Treat it as a proposal, not a published study.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fill &lt;code&gt;decisions.md&lt;/code&gt; before asking for more code.&lt;/li&gt;
&lt;li&gt;Generate against fixtures, never against live secrets.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;./scripts/local-gate.sh&lt;/code&gt; on your laptop.&lt;/li&gt;
&lt;li&gt;Treat any borrowed server as a scratch lane only.&lt;/li&gt;
&lt;li&gt;Merge only when the gate exits 0 and the table is complete.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I am not attaching latency charts on purpose. Charts without a pinned &lt;code&gt;reproduce.sh&lt;/code&gt; are theater.&lt;/p&gt;

&lt;p&gt;A tiny pytest file keeps the happy-path lie from shipping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tests/gate/test_fail_closed.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app.probe&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;probe&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_dead_socket_fails_closed&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;probe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tests/fixtures/min.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;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&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;timeout&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;connection_refused&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_retries_are_zero&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_timeout_is_reviewed&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout_s&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;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Does a dead socket fail closed on the first try? If that probe "succeeds," your gate is theater.&lt;/p&gt;

&lt;p&gt;Wire a stub module so the example is runnable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/config.py
&lt;/span&gt;&lt;span class="n"&gt;timeout_s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="n"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;workers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="c1"&gt;# app/probe.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;error&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="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;fixture_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fixture_path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base_url&lt;/span&gt;&lt;span class="sh"&gt;"&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;1&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="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;hostport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base_url&lt;/span&gt;&lt;span class="sh"&gt;"&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;1&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="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port_s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hostport&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="n"&gt;hostport&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;1&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="n"&gt;port&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;port_s&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;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_connection&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&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="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ok&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;error&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="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;OSError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timeout&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;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&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="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;connection_refused&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it. Watch the dead port refuse you. That refusal is the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a free model and free server fit
&lt;/h2&gt;

&lt;p&gt;I still use a throwaway lane for drafts. Scratch generation is fine. Merge authority is not.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;MonkeyCode offers free model access and a free server option. I treat that pair as the scratch lane: draft the probe, try the fixture, throw the box away. The local gate above still decides the merge. I am not claiming model names, quotas, hardware, or benchmarks. Those claims go stale. These five anti-patterns do not.&lt;/p&gt;

&lt;p&gt;If you want a disposable place to practice the gate, that scratch lane is enough. Do not promote it into CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;This catalog will annoy you on tiny scripts. Good. Friction on a one-line typo is a smell, not a virtue.&lt;/p&gt;

&lt;p&gt;It will not catch semantic bugs the tests never named. It will not replace a threat model. It will not make a weak fixture honest.&lt;/p&gt;

&lt;p&gt;Skip this approach when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You are pairing live and already running the suite&lt;/li&gt;
&lt;li&gt;The change is a comment, rename, or typo&lt;/li&gt;
&lt;li&gt;You cannot run anything locally at all&lt;/li&gt;
&lt;li&gt;You need a signed production deploy, not a laptop gate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cannot run locally? Then you have a bigger problem than chat quality. Borrowed servers drift. Free sessions forget. Your &lt;code&gt;reproduce.sh&lt;/code&gt; should not.&lt;/p&gt;

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

&lt;p&gt;I will not call the chat a review. I will not paste live keys into a prompt. I will not ship &lt;code&gt;retries=3&lt;/code&gt; because it looked standard.&lt;/p&gt;

&lt;p&gt;Would you bet an incident channel on a transcript? I would not.&lt;/p&gt;

&lt;p&gt;Close the tab after the gate is green. Not before.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>testing</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The Chat Approved It. Git Still Has No Receipt.</title>
      <dc:creator>Sam Chen</dc:creator>
      <pubDate>Fri, 18 Sep 2026 07:48:44 +0000</pubDate>
      <link>https://dev.to/codex_1135/the-chat-approved-it-git-still-has-no-receipt-2e9m</link>
      <guid>https://dev.to/codex_1135/the-chat-approved-it-git-still-has-no-receipt-2e9m</guid>
      <description>&lt;p&gt;Cheap inference does not replace a human review gate.&lt;br&gt;
It makes five skip patterns cheaper to repeat.&lt;br&gt;
I still catch them in my own branches.&lt;/p&gt;
&lt;h2&gt;
  
  
  What this piece is not
&lt;/h2&gt;

&lt;p&gt;This is not another vibe-coding culture rant.&lt;br&gt;
I will not grade your editor or your chat model.&lt;br&gt;
I will show symptoms, root causes, and replacements.&lt;/p&gt;

&lt;p&gt;The public debate this week is still noisy.&lt;br&gt;
People argue about whether AI writes real software.&lt;br&gt;
The useful question is smaller and much meaner.&lt;/p&gt;

&lt;p&gt;Did this diff actually get a review receipt?&lt;br&gt;
Or did a cheap run just feel like shipping?&lt;/p&gt;
&lt;h2&gt;
  
  
  The catalog at a glance
&lt;/h2&gt;

&lt;p&gt;I use the same three columns every time.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Symptom: what shows up in git or chat.&lt;/li&gt;
&lt;li&gt;Root cause: why cheap inference encourages the skip.&lt;/li&gt;
&lt;li&gt;Replacement: the pattern I want instead.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The proposed scripts and tables live right below.&lt;br&gt;
Treat the scripts as templates, not production gospel.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 1: Chat as the source of truth
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The pull request says "details in the thread."&lt;/li&gt;
&lt;li&gt;Commits read like "update stuff" and nothing else.&lt;/li&gt;
&lt;li&gt;Reviewers paste screenshots instead of file paths.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;The sidebar answered faster than the commit template.&lt;br&gt;
The answer felt documented because it was long.&lt;br&gt;
Git never stored the actual product decision.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement: put the decision in git
&lt;/h3&gt;

&lt;p&gt;I put the tradeoff in the commit body.&lt;br&gt;
I link the chat only as appendix, never as proof.&lt;br&gt;
If the chat dies, the repo must still explain the change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
reject unsigned webhook payloads

Why: the agent added a debug bypass on staging.
Decision: verify signatures in the wrapper, not the prompt.
Chat transcript is appendix only, not source.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask yourself one rude and practical question.&lt;br&gt;
Could a new teammate review this without the chat?&lt;br&gt;
If the answer is no, the chat still owns your history.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 2: Secrets riding the free prompt
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Env fragments appear inside a pasted stack trace.&lt;/li&gt;
&lt;li&gt;The prompt file contains hostnames from production.&lt;/li&gt;
&lt;li&gt;The agent reprints a token in the session log.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;A free box still feels disposable and unofficial.&lt;br&gt;
Disposable does not mean the prompt is private.&lt;br&gt;
Cheap inference can still persist that text somewhere.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement: redact, then hash
&lt;/h3&gt;

&lt;p&gt;I redact the raw prompt first, without exceptions.&lt;br&gt;
I hash the cleaned prompt file second.&lt;br&gt;
I never paste live credentials into a sandbox prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Proposed local redaction before any remote prompt.&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'s/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+/[redacted-email]/g'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'s/(api[_-]?key|token|secret)[=:][^[:space:]]+/\1=[redacted]/Ig'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'s/[0-9]{1,3}(\.[0-9]{1,3}){3}/[redacted-ip]/g'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  prompt.raw.txt &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; prompt.clean.txt

&lt;span class="nb"&gt;sha256sum &lt;/span&gt;prompt.clean.txt &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; prompt.clean.sha256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that before the model sees the file.&lt;br&gt;
If redaction fails, the session does not start.&lt;/p&gt;

&lt;p&gt;Would you paste that prompt into a public gist?&lt;br&gt;
If not, do not send it to a free endpoint either.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 3: The agent owns the lockfile
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The lockfile moves with a one-line behavior fix.&lt;/li&gt;
&lt;li&gt;The model tidies dependencies during a simple rename.&lt;/li&gt;
&lt;li&gt;Nobody can explain the version jump in review.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;Cheap retries invite extra cleanup turns from the agent.&lt;br&gt;
Those extra cleanup turns always love the lockfile.&lt;br&gt;
Lockfiles are easy to generate and hard to audit.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement: lockfile needs a human ACK
&lt;/h3&gt;

&lt;p&gt;I split lockfile diffs from the behavior diffs.&lt;br&gt;
I require an explicit ACK file in the pull request.&lt;br&gt;
No ACK means the CI job fails closed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# proposed_lockfile_ack.py
# Example only. Adapt paths before you trust it.
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;LOCK_NAMES&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;package-lock.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;poetry.lock&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;Cargo.lock&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;go.sum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;ACK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LOCKFILE_ACK.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;changed_files&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;set&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;out&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;check_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&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;diff&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;--name-only&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;origin/main...HEAD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&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="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&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;p&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="n"&gt;changed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;changed_files&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;touched_lock&lt;/span&gt; &lt;span class="o"&gt;=&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;changed&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;LOCK_NAMES&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;touched_lock&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;no lockfile change&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&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;ACK&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&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;lockfile changed without LOCKFILE_ACK.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;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ACK&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;i reviewed the lockfile diff&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text&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;ACK is missing the review sentence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;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;lockfile ACK present for:&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;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;touched_lock&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put that ACK in the same pull request.&lt;br&gt;
Name every package version that actually moved.&lt;br&gt;
Name the risk in one blunt sentence.&lt;/p&gt;

&lt;p&gt;Did the agent bump a compiler to make tests pass?&lt;br&gt;
That compiler bump is not just a tidy.&lt;br&gt;
That bump is a silent product change too.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 4: Policy living in the system prompt
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;"Do not call production" exists only in a prompt.&lt;/li&gt;
&lt;li&gt;The tool wrapper still accepts the prod hostname.&lt;/li&gt;
&lt;li&gt;Safety is a paragraph, not a check.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;Prompts are easy to type on a free model.&lt;br&gt;
Guards in code take an extra hour.&lt;br&gt;
Cheap inference rewards the hour you skip.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement: policy in code, prompt as hint
&lt;/h3&gt;

&lt;p&gt;I keep the system prompt short and polite.&lt;br&gt;
I keep the policy in a denylist.&lt;br&gt;
The model cannot override a failed hostname check.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# proposed_tool_guard.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlparse&lt;/span&gt;

&lt;span class="n"&gt;BLOCKED_HOST_SUFFIXES&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;.prod.internal&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;.corp.example&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;BLOCKED_HOSTS&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;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assert_safe_url&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;urlparse&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;hostname&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing host&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;host&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;BLOCKED_HOSTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&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;blocked host: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;host&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;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sfx&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;sfx&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;BLOCKED_HOST_SUFFIXES&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&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;blocked suffix: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;host&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;p&gt;The prompt may still say "stay in staging."&lt;br&gt;
The wrapper must still refuse the URL.&lt;br&gt;
Which layer do you trust when the model is tired?&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 5: Idle free capacity as a release signal
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The merge happened because the server was free tonight.&lt;/li&gt;
&lt;li&gt;The reviewer is whoever is still online in chat.&lt;/li&gt;
&lt;li&gt;Monday on-call inherits an unexplained diff.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;Cheap idle time feels like a wasted gift.&lt;br&gt;
That waste anxiety beats review patience every time.&lt;br&gt;
The review calendar never becomes a real gate.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement: capacity is not a criterion
&lt;/h3&gt;

&lt;p&gt;I keep capacity out of the ship list.&lt;br&gt;
A free server can wait until review exists.&lt;br&gt;
Idle machines do not approve the architecture change.&lt;/p&gt;

&lt;p&gt;Would you merge this if the box were busy?&lt;br&gt;
If the answer flips, capacity was the reviewer.&lt;br&gt;
Ship criteria belong in the receipt, not the queue.&lt;/p&gt;
&lt;h2&gt;
  
  
  The artifact: a review receipt
&lt;/h2&gt;

&lt;p&gt;I want one file per pull request.&lt;br&gt;
I call that file &lt;code&gt;REVIEW_RECEIPT.md&lt;/code&gt; on purpose.&lt;br&gt;
This file is not a model freeze file.&lt;/p&gt;

&lt;p&gt;A freeze file only pins the model inputs.&lt;br&gt;
This receipt pins the review skips I just named.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# REVIEW_RECEIPT.md&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Decision lives in commit: yes/no
&lt;span class="p"&gt;-&lt;/span&gt; Prompt redacted and hashed: yes/no (sha256: ...)
&lt;span class="p"&gt;-&lt;/span&gt; Lockfile ACK present: yes/no/n/a
&lt;span class="p"&gt;-&lt;/span&gt; Tool policy enforced in code: yes/no
&lt;span class="p"&gt;-&lt;/span&gt; Capacity was not a ship criterion: yes/no
&lt;span class="p"&gt;-&lt;/span&gt; Reviewer: name
&lt;span class="p"&gt;-&lt;/span&gt; Date: ISO-8601
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use this table when the sandbox looks too green.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claim you want&lt;/th&gt;
&lt;th&gt;Cheap sandbox can show&lt;/th&gt;
&lt;th&gt;Cheap sandbox cannot show&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Script parses on a clean clone&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Your dirty production working tree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unit tests pass with fixtures&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Real traffic shape&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool guard blocks fixture URLs&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;A hostname someone adds later&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt hash matches redacted text&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Extra secrets pasted only in chat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feature is safe to ship&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Data, auth, clocks, or load&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If the table cell says no, the merge still needs a human.&lt;/p&gt;

&lt;p&gt;Proposed checker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# proposed_receipt_check.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;receipt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REVIEW_RECEIPT.md&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="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&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;missing REVIEW_RECEIPT.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;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;required&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;Decision lives in commit:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Prompt redacted and hashed:&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;Lockfile ACK present:&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;Tool policy enforced in code:&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;Capacity was not a ship criterion:&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;Reviewer:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;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;line&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&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="n"&gt;text&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;missing&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;receipt missing fields:&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;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&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;search&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;sha256:\s*$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&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;I&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;M&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;hash field is empty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;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;review receipt fields present&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;Then you wire the checker into CI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# proposed .github/workflows/review-receipt.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;review-receipt&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;receipt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python proposed_receipt_check.py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have not executed this workflow on your org.&lt;br&gt;
Copy it only after you change the names.&lt;/p&gt;

&lt;h2&gt;
  
  
  A cheap rehearsal, not a release
&lt;/h2&gt;

&lt;p&gt;I still want a cheap place to practice the gate.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;MonkeyCode offers free model access and a free server option.&lt;br&gt;
I treat those as a rehearsal room for the receipt checker.&lt;br&gt;
I do not treat them as production capacity, an SLA, or a benchmark.&lt;/p&gt;

&lt;p&gt;If you rehearse there, run the redaction step first.&lt;br&gt;
Then run the receipt checker on a throwaway branch.&lt;br&gt;
Skip the demo prompt that never touches git.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this approach
&lt;/h2&gt;

&lt;p&gt;Do not use a free server for regulated customer data.&lt;br&gt;
Do not use prompt text as your only access control.&lt;br&gt;
Do not skip this catalog because the tokens were cheap.&lt;/p&gt;

&lt;p&gt;This receipt is only a minimum bar.&lt;br&gt;
It will not catch a clever exfil path.&lt;br&gt;
It will not replace a real threat model.&lt;/p&gt;

&lt;p&gt;Teams with no git history should not start here.&lt;br&gt;
You should fix basic source control first here.&lt;br&gt;
Then you add the review receipt file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;The checker only proves that a file exists.&lt;br&gt;
A bored reviewer can type "yes" everywhere.&lt;br&gt;
That is still a process failure, not a model failure.&lt;/p&gt;

&lt;p&gt;Cheap inference will keep getting even cheaper.&lt;br&gt;
Your gates will not appear by accident.&lt;br&gt;
Build them while the diffs are still small.&lt;/p&gt;

&lt;p&gt;Would I merge a green sandbox without the receipt?&lt;br&gt;
I would not merge that green branch yet.&lt;br&gt;
Would I trust the receipt without reading the diff?&lt;br&gt;
I would also refuse that lazy shortcut.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>security</category>
      <category>python</category>
    </item>
    <item>
      <title>The Agent Wrote the Tests. They Still Echoed the Patch.</title>
      <dc:creator>Sam Chen</dc:creator>
      <pubDate>Thu, 17 Sep 2026 05:44:43 +0000</pubDate>
      <link>https://dev.to/codex_1135/the-agent-wrote-the-tests-they-still-echoed-the-patch-4o35</link>
      <guid>https://dev.to/codex_1135/the-agent-wrote-the-tests-they-still-echoed-the-patch-4o35</guid>
      <description>&lt;p&gt;Passing tests do not prove the agent understood the contract.&lt;br&gt;
They often prove the agent quoted its own patch.&lt;br&gt;
That gap is the merge risk, not the model.&lt;/p&gt;

&lt;p&gt;I keep reviewing pull requests that look fully engineered.&lt;br&gt;
The prose stays calm while the checklist stays green.&lt;br&gt;
So why does production still surprise the on-call engineer?&lt;/p&gt;

&lt;p&gt;We launder chat-driven patches through review theater.&lt;br&gt;
Fluency becomes evidence and echoes become fake oracles.&lt;br&gt;
This catalog names four anti-patterns I keep seeing.&lt;/p&gt;

&lt;p&gt;Each entry has symptoms, a root cause, and a replacement.&lt;br&gt;
I also include a copyable oracle check and a command pin.&lt;br&gt;
Treat the snippets as labeled examples, not production history.&lt;/p&gt;
&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;p&gt;This is not a ranking of coding models today.&lt;br&gt;
This is not a claim that agents cannot write code.&lt;br&gt;
This is a review workflow you can run tomorrow.&lt;/p&gt;

&lt;p&gt;I am not publishing timings, winners, or customer stories.&lt;br&gt;
Those claims go stale and I will not invent them.&lt;br&gt;
If you need signed capacity data, use your own lab.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 1: Confidence as a merge gate
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The summary reads like a senior engineer wrote every line.&lt;/li&gt;
&lt;li&gt;Reviewers praise tone, structure, and seemingly clear reasoning.&lt;/li&gt;
&lt;li&gt;Nobody can name the invariant that actually changed here.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;Fluency is cheap while true invariants stay expensive.&lt;br&gt;
A model can narrate a patch without owning its failure.&lt;br&gt;
Your process then confuses narration with actual technical review.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;Require one written invariant before anyone opens the diff.&lt;br&gt;
If the author cannot state it, the PR remains a spike.&lt;br&gt;
Spikes get a ticket, a date, and no merge button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INVARIANT: refunds never exceed the captured amount
PROOF: test_refund_cap in tests/billing_oracle.py
OWNER: @oncall-owner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask one rude question during every model-authored review.&lt;br&gt;
What still fails if we delete the generated comments now?&lt;br&gt;
If the answer is nothing, you reviewed a blog post.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 2: Echo tests
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tests arrived in the same chat as the implementation.&lt;/li&gt;
&lt;li&gt;Assertions repeat literals copied from the generated body.&lt;/li&gt;
&lt;li&gt;Renaming a constant makes the test fail before production.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;The agent tested its story, not the external contract.&lt;br&gt;
The test file is a mirror sitting beside the patch.&lt;br&gt;
Green then only means I agreed with myself again.&lt;/p&gt;

&lt;p&gt;Did the test exist before the implementation existed?&lt;br&gt;
If not, you probably bought an echo, not an oracle.&lt;br&gt;
Oracles come from specs. Echoes come from stdout.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;Write the oracle before the implementation is allowed.&lt;br&gt;
Pin expected values from the spec, never from chat output.&lt;br&gt;
If you lack a spec, you still lack a real test.&lt;/p&gt;

&lt;p&gt;Here is a labeled example, not a measured suite.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# example_oracle.py — proposal, not a production run
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;

&lt;span class="n"&gt;SPEC_CAPTURED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;40.00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;SPEC_REQUESTED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;99.99&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;SPEC_REFUND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;40.00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# from the billing spec, not stdout
&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;refund_cap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;captured&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requested&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&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;Decimal&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;requested&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requested must be &amp;gt;= 0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;captured&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requested&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;test_refund_never_exceeds_capture&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;refund_cap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SPEC_CAPTURED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SPEC_REQUESTED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;SPEC_REFUND&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_oracle_does_not_quote_the_function&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;banned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assert refund_cap(SPEC_CAPTURED, SPEC_REQUESTED) == refund_cap&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;banned&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the example with a boring, repeatable command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest example_oracle.py &lt;span class="nt"&gt;-q&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your only assertion is output equals output, stop.&lt;br&gt;
That check is a quote. It is not a contract.&lt;/p&gt;
&lt;h3&gt;
  
  
  Echo versus oracle, on one page
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Echo test&lt;/th&gt;
&lt;th&gt;Oracle test&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source of expected value&lt;/td&gt;
&lt;td&gt;Generated code or chat&lt;/td&gt;
&lt;td&gt;Spec, RFC, or ticket&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same session as the patch&lt;/td&gt;
&lt;td&gt;Usually yes&lt;/td&gt;
&lt;td&gt;Must be no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rename a literal&lt;/td&gt;
&lt;td&gt;Test dies first&lt;/td&gt;
&lt;td&gt;Behavior still pinned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete the implementation comments&lt;/td&gt;
&lt;td&gt;Still green&lt;/td&gt;
&lt;td&gt;Still meaningful&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who can explain a failure&lt;/td&gt;
&lt;td&gt;The chat log&lt;/td&gt;
&lt;td&gt;A named owner&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Print that table in the PR template if you must.&lt;br&gt;
I would rather see one filled row than ten adjectives.&lt;br&gt;
Which column did your last AI patch actually occupy?&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 3: Prompt until green
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The chat log is longer than the resulting diff.&lt;/li&gt;
&lt;li&gt;Failures vanish after another polite try again.&lt;/li&gt;
&lt;li&gt;Nobody saved the first command that actually failed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;You optimized the prompt instead of pinning the system.&lt;br&gt;
The failure never became a hashed, repeatable command.&lt;br&gt;
The next model, or the next morning, revives it.&lt;/p&gt;

&lt;p&gt;Is the fix a new behavior, or a new pep talk?&lt;br&gt;
If you cannot tell, you do not have a fix.&lt;br&gt;
You have a mood swing with a green badge.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;Freeze the failing command, not the motivational thread.&lt;br&gt;
Re-run that exact command after every prompt change.&lt;br&gt;
If the command drifts, the supposed fix is theater.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# pin the failure; do not paraphrase it in chat&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/failing_cmd.sh &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/usr/bin/env bash
set -euo pipefail
python -m pytest tests/billing_oracle.py -q
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /tmp/failing_cmd.sh
&lt;span class="nb"&gt;sha256sum&lt;/span&gt; /tmp/failing_cmd.sh
/tmp/failing_cmd.sh&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;EXIT:&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste the hash into the pull request body.&lt;br&gt;
Did this command change, or did the behavior change?&lt;br&gt;
Only the second answer counts as an actual fix.&lt;/p&gt;

&lt;p&gt;Keep the script in the repo when the check matters.&lt;br&gt;
A gist in chat will rot before the next on-call shift.&lt;br&gt;
CI should run the same bytes your laptop ran.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; scripts
&lt;span class="nb"&gt;cp&lt;/span&gt; /tmp/failing_cmd.sh scripts/oracle_refund.sh
git add scripts/oracle_refund.sh tests/billing_oracle.py
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"pin refund oracle and the failing command"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Anti-pattern 4: Sandbox as production proof
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;It ran on a free box, so we called it done.&lt;/li&gt;
&lt;li&gt;No owner, no data class, no rollback, no page path.&lt;/li&gt;
&lt;li&gt;The demo path is the only path anyone executed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;A disposable environment proves one thing: reproducibility.&lt;br&gt;
It does not prove capacity, identity, or blast radius.&lt;br&gt;
People collapse those proofs because the demo felt good.&lt;/p&gt;

&lt;p&gt;Would you page this sandbox at three in the morning?&lt;br&gt;
If the answer is no, it never signed production.&lt;br&gt;
Stop letting a clean demo launder an unsigned change.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement
&lt;/h3&gt;

&lt;p&gt;Split the checks. Keep them boring and explicitly named.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Sandbox may answer&lt;/th&gt;
&lt;th&gt;Production still needs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Does the oracle fail closed?&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Same oracle in CI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can a stranger reproduce it?&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;README command plus hash&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Will it hold real load?&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Your own capacity test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who gets paged at 03:00?&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;A named owner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is this data class allowed?&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Policy, not a chat&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I use a free coding environment only as a reproduction box.&lt;br&gt;
MonkeyCode is an open source option with free model access and a free server. Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;That box can rerun &lt;code&gt;scripts/oracle_refund.sh&lt;/code&gt; from a clean tree.&lt;br&gt;
It cannot sign your incident, your identity, or customer data.&lt;br&gt;
If you need those signatures, you already left the sandbox.&lt;/p&gt;
&lt;h2&gt;
  
  
  A workflow you can copy tomorrow
&lt;/h2&gt;

&lt;p&gt;Do this on the next model-authored pull request.&lt;br&gt;
Do not skip the writing. The writing is the review.&lt;br&gt;
Empty fields mean you are still in chat-driven mode.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;State the invariant in one plain sentence.&lt;/li&gt;
&lt;li&gt;Add one oracle that never imports patch literals.&lt;/li&gt;
&lt;li&gt;Pin the failing command and store its sha256.&lt;/li&gt;
&lt;li&gt;Mark sandbox results as reproduced, never shipped.&lt;/li&gt;
&lt;li&gt;Name an owner who can revert without the chat log.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PR template
- Invariant:
- Oracle test path:
- Failing command hash:
- Reproduced on (sandbox/CI):
- Ship check (yes/no + owner):
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Fill it in public comments, not in a private sidebar.&lt;br&gt;
If any field is empty, the merge is still a spike.&lt;br&gt;
Call it a spike. Do not call it engineering.&lt;/p&gt;

&lt;p&gt;Want a tiny gate before humans even look?&lt;br&gt;
This hook only checks that the template fields exist.&lt;br&gt;
It will not prove the oracle is wise. Nothing local can.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# scripts/check_pr_template.sh — proposal&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;pr_body&lt;/span&gt;&lt;span class="p"&gt;.md&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;key &lt;span class="k"&gt;in &lt;/span&gt;Invariant &lt;span class="s2"&gt;"Oracle test path"&lt;/span&gt; &lt;span class="s2"&gt;"Failing command hash"&lt;/span&gt; &lt;span class="s2"&gt;"Ship check"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"^- &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-vq&lt;/span&gt; &lt;span class="s2"&gt;"^- &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: *$"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Who should not use this
&lt;/h2&gt;

&lt;p&gt;Do not use this catalog as a reason to ban models.&lt;br&gt;
Do not use a free server as a load or soak test.&lt;br&gt;
Do not paste secrets, prod dumps, or customer records there.&lt;/p&gt;

&lt;p&gt;Do not treat my examples as measured benchmarks either.&lt;br&gt;
I did not name models, quotas, hardware, or durations.&lt;br&gt;
Those numbers rot. These anti-patterns do not rot.&lt;/p&gt;

&lt;p&gt;Skip this sermon when the change is already specified.&lt;br&gt;
A owned RFC with golden tests does not need this ritual.&lt;br&gt;
You already have an oracle. Keep feeding it in CI.&lt;/p&gt;

&lt;p&gt;Also skip it for throwaway spikes you will delete today.&lt;br&gt;
Just do not merge the spike because the prose sounded sure.&lt;br&gt;
Sounding sure is the cheapest part of the whole stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;Short review questions will not catch every failure mode.&lt;br&gt;
Oracles are wrong when the spec itself is wrong.&lt;br&gt;
Pinned commands rot if nobody runs them inside CI.&lt;/p&gt;

&lt;p&gt;This workflow also slows a true exploratory spike.&lt;br&gt;
That cost is the point, not an accident of process.&lt;br&gt;
If the slowdown hurts, you were shipping narration.&lt;/p&gt;

&lt;p&gt;I cannot prove your billing rules from a blog example.&lt;br&gt;
Your domain invariants have to come from your own spec.&lt;br&gt;
Copy the method. Do not copy my refund numbers blindly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually want in review
&lt;/h2&gt;

&lt;p&gt;I want a sentence a stranger can falsify later.&lt;br&gt;
I want a test that would fail if the spec changed.&lt;br&gt;
I want a command hash that does not depend on charm.&lt;/p&gt;

&lt;p&gt;Did you review behavior, or did you only review tone?&lt;br&gt;
Did the tests quote the patch, or pin a contract?&lt;br&gt;
If you cannot tell, the agent did not fail. Review did.&lt;/p&gt;

&lt;p&gt;Pin the command. Name the owner. Then consider merge.&lt;br&gt;
That is the whole method, and it still fits in a template.&lt;br&gt;
The model can draft. It still cannot sign the incident.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>The Model Replied. You Still Have No Freeze File.</title>
      <dc:creator>Sam Chen</dc:creator>
      <pubDate>Wed, 16 Sep 2026 04:03:43 +0000</pubDate>
      <link>https://dev.to/codex_1135/the-model-replied-you-still-have-no-freeze-file-1cc9</link>
      <guid>https://dev.to/codex_1135/the-model-replied-you-still-have-no-freeze-file-1cc9</guid>
      <description>&lt;p&gt;A green chat is not a green build.&lt;br&gt;
The missing freeze file is the real outage.&lt;br&gt;
Free remote inference does not pin a compiler.&lt;/p&gt;

&lt;p&gt;The public feed keeps arguing about model skill.&lt;br&gt;
Did AI already beat most working developers?&lt;br&gt;
That question skips the only check I run.&lt;/p&gt;

&lt;p&gt;Did you pin the run at all?&lt;br&gt;
Or did you screenshot a lucky chat?&lt;br&gt;
I keep a blunt catalog for this failure.&lt;/p&gt;

&lt;p&gt;Each entry lists symptoms and a root cause.&lt;br&gt;
Each one also names a replacement pattern.&lt;br&gt;
I am not ranking vendors in this post.&lt;/p&gt;

&lt;p&gt;I am not posting latency or quality charts.&lt;br&gt;
I am talking about unpinned AI coding runs.&lt;br&gt;
Folklore patches still look brilliant in a thread.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I mean by a freeze file
&lt;/h2&gt;

&lt;p&gt;A freeze file is a local contract.&lt;br&gt;
It records what the model actually saw.&lt;br&gt;
It also records the patch you accepted.&lt;/p&gt;

&lt;p&gt;The proposed schema sits below.&lt;br&gt;
Treat it as an example until you execute it.&lt;br&gt;
Do not paste it into production CI untested.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt_sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"replace-me"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"patch_sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"replace-me"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"test_command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pytest -q tests/test_billing.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-16T12:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"runtime_kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scratch-pad"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reproduced_locally"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"secrets_present"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I only trust prompt, patch, and test hashes.&lt;br&gt;
Drop one hash and the run becomes folklore.&lt;br&gt;
Git then stores a story, not a replay.&lt;/p&gt;

&lt;p&gt;I gate merges on that file.&lt;br&gt;
No freeze file, no merge.&lt;br&gt;
The validator is boring on purpose.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Refuse an AI coding run with no freeze file.

Local gate only. It never calls a model.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;REQUIRED&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;prompt_sha256&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;patch_sha256&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;test_command&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;created_at&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;runtime_kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ALLOWED_RUNTIME&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;local&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;scratch-pad&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;ci-pinned&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load&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;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&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="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freeze file must be an object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&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;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;REQUIRED&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;data&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="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;errors&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&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;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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;runtime_kind&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;runtime&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ALLOWED_RUNTIME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;errors&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;runtime_kind &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s"&gt; is not pinable&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;runtime&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scratch-pad&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;data&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;reproduced_locally&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;errors&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scratch-pad run was never reproduced locally&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;data&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;secrets_present&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&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;errors&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freeze file claims secrets in the prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&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;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;2&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;usage: validate_freeze.py path/to/freeze.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&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="s"&gt;no freeze file at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&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="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate&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;path&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;errors&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;unpinned run:&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;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;errors&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="s"&gt;- &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&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;return&lt;/span&gt; &lt;span class="mi"&gt;1&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;freeze file ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the local gate like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sha256sum &lt;/span&gt;prompt.txt patch.diff
python3 validate_freeze.py freeze.json
pytest &lt;span class="nt"&gt;-q&lt;/span&gt; tests/test_billing.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want a shorter rule of thumb?&lt;br&gt;
If &lt;code&gt;validate_freeze.py&lt;/code&gt; cannot run, you vibe-coded.&lt;br&gt;
The chat UI will not argue with that.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 1: Unpinned inference
&lt;/h2&gt;

&lt;p&gt;You asked the box. It answered.&lt;br&gt;
You shipped the vibe anyway.&lt;br&gt;
You never named the runtime kind.&lt;/p&gt;
&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tomorrow's answer flips on the same prompt.&lt;/li&gt;
&lt;li&gt;Nobody can name the runtime kind.&lt;/li&gt;
&lt;li&gt;"Works on my machine" now means "works in chat."&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;You treated a remote completion like &lt;code&gt;gcc -v&lt;/code&gt;.&lt;br&gt;
Compilers publish versions you can pin.&lt;br&gt;
Many chat boxes do not.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement pattern
&lt;/h3&gt;

&lt;p&gt;Record &lt;code&gt;runtime_kind&lt;/code&gt; before you accept a patch.&lt;br&gt;
Reproduce the patch with local tests.&lt;br&gt;
Refuse to merge folklore from a thread.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; .runtime_kind freeze.json
&lt;span class="c"&gt;# expected: local | scratch-pad | ci-pinned&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did the model get smarter overnight?&lt;br&gt;
Or did the unnamed runtime drift?&lt;br&gt;
I assume drift until the freeze says otherwise.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 2: Chat log as the audit trail
&lt;/h2&gt;

&lt;p&gt;The thread looks complete and friendly.&lt;br&gt;
It even has jokes in the margins.&lt;br&gt;
Auditors cannot replay jokes later.&lt;/p&gt;
&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The only spec lives in a vendor UI.&lt;/li&gt;
&lt;li&gt;Patch files have no prompt hash.&lt;/li&gt;
&lt;li&gt;New hires cannot reconstruct the change.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;You stored conversation, not evidence.&lt;br&gt;
A chat log is not an artifact.&lt;br&gt;
It is a diary with extra tokens.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement pattern
&lt;/h3&gt;

&lt;p&gt;Export three files, always, in one commit.&lt;br&gt;
Keep &lt;code&gt;prompt.txt&lt;/code&gt;, &lt;code&gt;patch.diff&lt;/code&gt;, and &lt;code&gt;freeze.json&lt;/code&gt;.&lt;br&gt;
Then hash them before anyone reviews the patch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add prompt.txt patch.diff freeze.json
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"pin billing patch to freeze file"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did the assistant summarize the diff for you?&lt;br&gt;
I do not care about that summary.&lt;br&gt;
I care whether &lt;code&gt;patch_sha256&lt;/code&gt; matches &lt;code&gt;git diff&lt;/code&gt;.&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="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import hashlib, pathlib, json, sys
patch = pathlib.Path("patch.diff").read_bytes()
digest = hashlib.sha256(patch).hexdigest()
frozen = json.loads(pathlib.Path("freeze.json").read_text())
sys.exit(0 if frozen.get("patch_sha256") == digest else 1)
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Anti-pattern 3: Free server as CI
&lt;/h2&gt;

&lt;p&gt;Exploration is cheap, and that is fine.&lt;br&gt;
CI is a different machine with different trust.&lt;br&gt;
Do not blur those two on purpose.&lt;/p&gt;

&lt;p&gt;I still want a scratch pad for messy prompts.&lt;br&gt;
Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;br&gt;
MonkeyCode offers free model access and a free server option.&lt;/p&gt;

&lt;p&gt;I map that pair to &lt;code&gt;runtime_kind: scratch-pad&lt;/code&gt; only.&lt;br&gt;
Draft there if you need a pad.&lt;br&gt;
Then copy the patch home and run pytest locally.&lt;/p&gt;
&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Shared scratch boxes absorb flaky tests.&lt;/li&gt;
&lt;li&gt;Failures get blamed on "the model."&lt;/li&gt;
&lt;li&gt;Secrets leak into a communal prompt.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;You confused availability with isolation.&lt;br&gt;
A free server is a shared whiteboard.&lt;br&gt;
Whiteboards do not sign releases.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement pattern
&lt;/h3&gt;

&lt;p&gt;Keep the scratch pad in the draft lane.&lt;br&gt;
Never point &lt;code&gt;test_command&lt;/code&gt; at a shared box.&lt;br&gt;
Set &lt;code&gt;reproduced_locally&lt;/code&gt; only after local pytest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"runtime_kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scratch-pad"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reproduced_locally"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"test_command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pytest -q tests/test_billing.py"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;reproduced_locally&lt;/code&gt; is still false, stop.&lt;br&gt;
You have a sketch, not a candidate patch.&lt;br&gt;
The validator will say the same thing.&lt;/p&gt;
&lt;h2&gt;
  
  
  Anti-pattern 4: Tests written after the patch
&lt;/h2&gt;

&lt;p&gt;The model "solved" the ticket first.&lt;br&gt;
Then you asked it for tests.&lt;br&gt;
The tests learned the first answer.&lt;/p&gt;
&lt;h3&gt;
  
  
  Symptoms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tests assert the first returned string.&lt;/li&gt;
&lt;li&gt;Edge cases never appear in the file.&lt;/li&gt;
&lt;li&gt;Refactors break tests that never encoded rules.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;You used tests as applause.&lt;br&gt;
Applause is not a specification.&lt;br&gt;
The model taught the oracle.&lt;/p&gt;
&lt;h3&gt;
  
  
  Replacement pattern
&lt;/h3&gt;

&lt;p&gt;Write the failing test first.&lt;br&gt;
Commit it on &lt;code&gt;main&lt;/code&gt; before any prompt.&lt;br&gt;
Only then paste the prompt that cites 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="c1"&gt;# tests/test_billing.py — proposed example, not executed here
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_prorate_ignores_trailing_partial_day&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;prorate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-09-01&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;2026-09-16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this loop without skipping step one.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;pytest&lt;/code&gt; must fail on &lt;code&gt;main&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Freeze the prompt that cites that test.&lt;/li&gt;
&lt;li&gt;Accept a patch only if the same test passes.&lt;/li&gt;
&lt;li&gt;Re-run &lt;code&gt;python3 validate_freeze.py freeze.json&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If step one was already green, you cheated.&lt;br&gt;
The model did not invent your spec.&lt;br&gt;
You let it grade its own homework.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision table I actually use
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What I saw&lt;/th&gt;
&lt;th&gt;Story I tell myself&lt;/th&gt;
&lt;th&gt;Anti-pattern&lt;/th&gt;
&lt;th&gt;Replacement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Same prompt, new answer&lt;/td&gt;
&lt;td&gt;The model got worse&lt;/td&gt;
&lt;td&gt;Unpinned inference&lt;/td&gt;
&lt;td&gt;Freeze runtime, replay locally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long chat history&lt;/td&gt;
&lt;td&gt;We have an audit&lt;/td&gt;
&lt;td&gt;Chat log as audit&lt;/td&gt;
&lt;td&gt;Commit prompt, patch, freeze&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests on a shared box&lt;/td&gt;
&lt;td&gt;CI is covered&lt;/td&gt;
&lt;td&gt;Free server as CI&lt;/td&gt;
&lt;td&gt;Local pytest gate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests after the patch&lt;/td&gt;
&lt;td&gt;We are thorough&lt;/td&gt;
&lt;td&gt;Post-hoc tests&lt;/td&gt;
&lt;td&gt;Failing test first&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Print that table next to the PR.&lt;br&gt;
If a row matches, name the anti-pattern.&lt;br&gt;
Do not argue about model IQ instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete debugging workflow
&lt;/h2&gt;

&lt;p&gt;I use this when a "smart" patch rots.&lt;br&gt;
It is a proposed workflow, not a benchmark.&lt;br&gt;
I have not executed it against your repo.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Print the freeze file. Missing file means stop.&lt;/li&gt;
&lt;li&gt;Check &lt;code&gt;secrets_present&lt;/code&gt;. True means rotate keys.&lt;/li&gt;
&lt;li&gt;Confirm &lt;code&gt;reproduced_locally&lt;/code&gt; is actually true.&lt;/li&gt;
&lt;li&gt;Replay &lt;code&gt;test_command&lt;/code&gt; on a dirty checkout.&lt;/li&gt;
&lt;li&gt;If replay fails, blame the freeze, not the model.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; freeze.json &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"unpinned"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
python3 validate_freeze.py freeze.json
git stash push &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"temp"&lt;/span&gt;
bash &lt;span class="nt"&gt;-lc&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; .test_command freeze.json&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why stash first?&lt;br&gt;
Because dirty trees hide lucky passes.&lt;br&gt;
I want the freeze to survive a clean tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;A freeze file is not magic.&lt;br&gt;
Hashing a prompt does not freeze a remote model.&lt;br&gt;
The validator cannot see server-side drift.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;scratch-pad&lt;/code&gt; is an honesty label.&lt;br&gt;
It does not claim isolation or uptime.&lt;br&gt;
It does not claim a service level.&lt;/p&gt;

&lt;p&gt;Do not use this approach when you need these.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production traffic on the same box&lt;/li&gt;
&lt;li&gt;Secret-bearing prompts on a shared server&lt;/li&gt;
&lt;li&gt;A contractual SLA for inference&lt;/li&gt;
&lt;li&gt;A merge that skips local replay&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regulated workflows need a runtime you control.&lt;br&gt;
This catalog will not give you that.&lt;br&gt;
It will only stop you lying to git.&lt;/p&gt;

&lt;p&gt;Solo explorers can skip the freeze for toys.&lt;br&gt;
Throwaway katas do not need &lt;code&gt;freeze.json&lt;/code&gt;.&lt;br&gt;
Anything you might merge tomorrow does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I ship
&lt;/h2&gt;

&lt;p&gt;I ship a patch, a test, and a freeze file.&lt;br&gt;
I do not ship a chat screenshot.&lt;br&gt;
A green chat is still an unpinned run.&lt;/p&gt;

&lt;p&gt;If the freeze is missing, I failed.&lt;br&gt;
The model did not fail me first.&lt;br&gt;
Pin the run or admit you vibe-coded.&lt;/p&gt;

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