<?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: Jordan Huang</title>
    <description>The latest articles on DEV Community by Jordan Huang (@gitlab_3188).</description>
    <link>https://dev.to/gitlab_3188</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%2F4066558%2F3018daf5-9f41-49c5-89c9-a0dadd1551c2.png</url>
      <title>DEV Community: Jordan Huang</title>
      <link>https://dev.to/gitlab_3188</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gitlab_3188"/>
    <language>en</language>
    <item>
      <title>FAQ: Your Free Server Closed. What Actually Shipped?</title>
      <dc:creator>Jordan Huang</dc:creator>
      <pubDate>Sun, 20 Sep 2026 07:31:23 +0000</pubDate>
      <link>https://dev.to/gitlab_3188/faq-your-free-server-closed-what-actually-shipped-3iol</link>
      <guid>https://dev.to/gitlab_3188/faq-your-free-server-closed-what-actually-shipped-3iol</guid>
      <description>&lt;p&gt;You closed the tab. Did any patch survive?&lt;/p&gt;

&lt;p&gt;I keep hearing the same claims. An agent ran on a free box. The prompt said done. Someone treats that as shipped work. That is a story, not a receipt.&lt;/p&gt;

&lt;p&gt;This FAQ is about promotion. How does work leave a scratch session? What still counts after the process dies?&lt;/p&gt;

&lt;p&gt;I use first-person here as a reviewer. I am not claiming a production outage. Every command below is a proposed check. Run it. Do not trust my memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this keeps happening
&lt;/h2&gt;

&lt;p&gt;Agents now live on rented shells. Your laptop is not the only cwd. That is useful. It is also a trap.&lt;/p&gt;

&lt;p&gt;A session can look complete and still own nothing durable. Git never saw the files. CI never saw the SHA. Your laptop never fetched the branch.&lt;/p&gt;

&lt;p&gt;I sometimes park that loop on MonkeyCode. Disclosure: This article was prepared as part of MonkeyCode's product outreach. It offers free model access and a free server option. That is all I will claim. No model names. No quotas. No hardware story.&lt;/p&gt;

&lt;p&gt;The product is not the point. The receipt is the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q1. The remote prompt said success. Is that a merge receipt?
&lt;/h2&gt;

&lt;p&gt;No. A sentence is not &lt;code&gt;git&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Ask a ruder question. What refs exist after the praise?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rev-parse &lt;span class="nt"&gt;--abbrev-ref&lt;/span&gt; HEAD
git rev-parse HEAD
git status &lt;span class="nt"&gt;--porcelain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v1
git log &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'%H %D %s'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;HEAD&lt;/code&gt; is detached, you do not have a branch. If porcelain is empty, maybe the tree is clean. Maybe you are in the wrong clone.&lt;/p&gt;

&lt;p&gt;Corrected model: success text is narration. A merge receipt is a SHA on a named ref that &lt;code&gt;origin&lt;/code&gt; also has.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rev-parse HEAD
git rev-parse @&lt;span class="o"&gt;{&lt;/span&gt;u&lt;span class="o"&gt;}&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'no upstream'&lt;/span&gt;
git cherry &lt;span class="nt"&gt;-v&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No upstream? Nothing shipped. Stop talking about done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q2. Local HEAD matches origin. Am I finished?
&lt;/h2&gt;

&lt;p&gt;Matching SHAs can still hide a dirty tree. People skip porcelain. Then they ship a lie.&lt;/p&gt;

&lt;p&gt;Check three planes, not one hash.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Committed tree: &lt;code&gt;git rev-parse HEAD&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Index: &lt;code&gt;git diff --cached --stat&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Worktree: &lt;code&gt;git diff --stat&lt;/code&gt; plus untracked files
&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;echo&lt;/span&gt; &lt;span class="s2"&gt;"HEAD &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;"INDEX"&lt;/span&gt;
git diff &lt;span class="nt"&gt;--cached&lt;/span&gt; &lt;span class="nt"&gt;--name-only&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"WORKTREE"&lt;/span&gt;
git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"UNTRACKED"&lt;/span&gt;
git ls-files &lt;span class="nt"&gt;--others&lt;/span&gt; &lt;span class="nt"&gt;--exclude-standard&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same SHA, dirty tree? You verified a commit, not the session. The agent may have written files after the commit. Those files die with the box.&lt;/p&gt;

&lt;p&gt;Corrected model: a SHA is a photograph. A dirty tree is a room you have not photographed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q3. Untracked output on the free box counts as delivered?
&lt;/h2&gt;

&lt;p&gt;Counts for whom? The disk that will vanish?&lt;/p&gt;

&lt;p&gt;Agents love &lt;code&gt;/tmp&lt;/code&gt;, &lt;code&gt;./out&lt;/code&gt;, and &lt;code&gt;pytest&lt;/code&gt; cache folders. They print paths. Humans screenshot paths. Nobody adds the path to git.&lt;/p&gt;

&lt;p&gt;Proposed sniff for junk that feels like a deliverable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-maxdepth&lt;/span&gt; 3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="se"&gt;\(&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.xml'&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;'*.html'&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;'coverage*'&lt;/span&gt; &lt;span class="se"&gt;\&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;'dist'&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;'build'&lt;/span&gt; &lt;span class="se"&gt;\)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-not&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; &lt;span class="s1"&gt;'./.git/*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Label this as a hunt, not a linter. Tune the names. Then decide, file by file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commit it, because reviewers need it.&lt;/li&gt;
&lt;li&gt;Upload it, because CI already rebuilds it.&lt;/li&gt;
&lt;li&gt;Drop it, because it is noise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Corrected model: if git cannot clone it, you did not deliver it. A free server is not an artifact store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q4. I ran it on the free server. Will CI match?
&lt;/h2&gt;

&lt;p&gt;Maybe the tests passed. Which Python? Which locale? Which &lt;code&gt;PATH&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Do not argue. Dump the runner identity into a file you actually keep.&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="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"date_utc &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y-%m-%dT%H:%M:%SZ&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;"pwd &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&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;"uid &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; gid &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt;&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;"uname &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;&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;"shell &lt;/span&gt;&lt;span class="nv"&gt;$SHELL&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;"path &lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; python3 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'import sys,locale; print(sys.version); print(locale.getlocale())'&lt;/span&gt;
  &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; node &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; node &lt;span class="nt"&gt;-v&lt;/span&gt;
  git &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; session_identity.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit that file only if your team wants runner forensics. Otherwise keep it next to the PR comment. Compare it to CI logs later.&lt;/p&gt;

&lt;p&gt;PATH myths die here. Your laptop uses &lt;code&gt;pyenv&lt;/code&gt;. The box uses distro Python. Both print green. They are not the same experiment.&lt;/p&gt;

&lt;p&gt;Corrected model: a passing command is bound to an identity dump. No dump, no comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q5. Closing the session archives the evidence?
&lt;/h2&gt;

&lt;p&gt;Closing a tab is not &lt;code&gt;git push&lt;/code&gt;. It is not &lt;code&gt;tar&lt;/code&gt;. It is not CI cache.&lt;/p&gt;

&lt;p&gt;I treat session end like a power loss. Anything not pushed is gone. That includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rebase state in &lt;code&gt;.git&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;stash entries nobody named&lt;/li&gt;
&lt;li&gt;hook output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.env&lt;/code&gt; the agent invented&lt;/li&gt;
&lt;li&gt;test databases in &lt;code&gt;/tmp&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Proposed last-minute grab, still unexecuted until you run it:&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: run before you kill the session&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/receipts"&lt;/span&gt;
&lt;span class="nv"&gt;OUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/receipts/receipt-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y%m%dT%H%M%SZ&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.txt"&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  git remote &lt;span class="nt"&gt;-v&lt;/span&gt;
  git status &lt;span class="nt"&gt;--porcelain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v1 &lt;span class="nt"&gt;-b&lt;/span&gt;
  git stash list
  git diff &lt;span class="nt"&gt;--stat&lt;/span&gt;
  git log &lt;span class="nt"&gt;-5&lt;/span&gt; &lt;span class="nt"&gt;--oneline&lt;/span&gt; &lt;span class="nt"&gt;--decorate&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&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;"wrote &lt;/span&gt;&lt;span class="nv"&gt;$OUT&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 free server deletes &lt;code&gt;$HOME&lt;/code&gt; on stop, this file dies too. Then the only archive is a push. Push or lose it. That is the whole FAQ.&lt;/p&gt;

&lt;p&gt;Corrected model: archive means another machine can fetch it. Chat history is not that machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Artifact: a session receipt you can diff
&lt;/h2&gt;

&lt;p&gt;Here is a small proposed script. I have not benchmarked it. It writes a receipt. You copy that receipt off the box. Then you diff two receipts.&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;# session_receipt.sh — proposed helper, not a product claim&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;root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--show-toplevel&lt;/span&gt; 2&amp;gt;/dev/null &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="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$root&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;"not a git worktree"&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;2
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;receipt&lt;/span&gt;&lt;span class="o"&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="p"&gt;/tmp/session-receipt.json&lt;/span&gt;&lt;span class="k"&gt;}&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;' "&lt;/span&gt;&lt;span class="nv"&gt;$receipt&lt;/span&gt;&lt;span class="sh"&gt;"
import json, os, subprocess, sys, time

def sh(args):
    p = subprocess.run(args, text=True, capture_output=True)
    return p.returncode, (p.stdout or "").rstrip("&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"), (p.stderr or "").rstrip("&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;")

def out(args):
    code, stdout, _ = sh(args)
    return stdout if code == 0 else ""

receipt_path = sys.argv[1]
head = out(["git", "rev-parse", "HEAD"])
branch = out(["git", "rev-parse", "--abbrev-ref", "HEAD"])
upstream = out(["git", "rev-parse", "@{u}"])
porcelain = out(["git", "status", "--porcelain=v1"])
untracked = out(["git", "ls-files", "--others", "--exclude-standard"])

payload = {
    "generated_at_unix": int(time.time()),
    "cwd": os.getcwd(),
    "head": head,
    "branch": branch,
    "upstream": upstream or None,
    "dirty": bool(porcelain),
    "porcelain": porcelain.splitlines() if porcelain else [],
    "untracked": untracked.splitlines() if untracked else [],
    "head_subject": out(["git", "log", "-1", "--format=%s"]),
}
with open(receipt_path, "w", encoding="utf-8") as f:
    json.dump(payload, f, indent=2)
    f.write("&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;")
print(receipt_path)
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage I actually want in a PR comment:&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;chmod&lt;/span&gt; +x session_receipt.sh
./session_receipt.sh /tmp/session-receipt.json
&lt;span class="c"&gt;# then copy the json off the box before logout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Diff two receipts without drama:&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, sys
a = json.load(open(sys.argv[1]))
b = json.load(open(sys.argv[2]))
keys = ["head", "branch", "upstream", "dirty"]
for k in keys:
    if a.get(k) != b.get(k):
        print(f"MISMATCH {k}: {a.get(k)!r} vs {b.get(k)!r}")
print("untracked_a", a.get("untracked"))
print("untracked_b", b.get("untracked"))
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;span class="c"&gt;# python3 compare.py laptop.json server.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;head&lt;/code&gt; matches and &lt;code&gt;dirty&lt;/code&gt; is false on both, you may talk about shipping. If untracked lists differ, you are still arguing about files that git does not know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision table: what still counts
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claim you heard&lt;/th&gt;
&lt;th&gt;Evidence that would support it&lt;/th&gt;
&lt;th&gt;If evidence is missing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"The agent finished."&lt;/td&gt;
&lt;td&gt;Named branch, clean porcelain, pushed SHA&lt;/td&gt;
&lt;td&gt;Treat as a draft on a dying disk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Tests ran."&lt;/td&gt;
&lt;td&gt;CI log for that SHA, not session stdout&lt;/td&gt;
&lt;td&gt;Re-run on a runner you control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Artifact is included."&lt;/td&gt;
&lt;td&gt;Path in &lt;code&gt;git ls-files&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Rebuild or drop the claim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Same as my laptop."&lt;/td&gt;
&lt;td&gt;Two receipts, matching &lt;code&gt;head&lt;/code&gt; and identity dump&lt;/td&gt;
&lt;td&gt;You compared vibes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"We archived the session."&lt;/td&gt;
&lt;td&gt;Fetchable URL or git remote&lt;/td&gt;
&lt;td&gt;You archived a feeling&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. It kills most arguments in one scan.&lt;/p&gt;

&lt;h2&gt;
  
  
  A workflow I will actually follow
&lt;/h2&gt;

&lt;p&gt;Short loop. No theatre.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start the agent on the free server if you want cheap iteration.&lt;/li&gt;
&lt;li&gt;Force every "done" through &lt;code&gt;session_receipt.sh&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Push a named branch before you close anything.&lt;/li&gt;
&lt;li&gt;Copy the receipt to your laptop.&lt;/li&gt;
&lt;li&gt;Run the same receipt locally.&lt;/li&gt;
&lt;li&gt;Only then paste a SHA into review.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Skip a step and you are demoing a ghost.&lt;/p&gt;

&lt;p&gt;Want a brutal extra? Delete the remote working tree in your head the moment you log out. If the branch is not on &lt;code&gt;origin&lt;/code&gt;, it never existed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin
git rev-parse origin/your-branch
&lt;span class="c"&gt;# fail here? your free server did not ship&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;This receipt is git-shaped. It will not catch data you wrote to a hosted database. It will not catch secrets in shell history. It will not freeze container layers.&lt;/p&gt;

&lt;p&gt;It also will not make two Pythons identical. Identity dumps explain mismatch. They do not fix mismatch.&lt;/p&gt;

&lt;p&gt;Do not commit receipts that leak host names you cannot share. Redact. Or keep them in the PR, not the repo.&lt;/p&gt;

&lt;p&gt;The script uses &lt;code&gt;python3&lt;/code&gt; and &lt;code&gt;git&lt;/code&gt;. If either is missing, the receipt is incomplete. That failure is useful. It means the box is not the runner you imagined.&lt;/p&gt;

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

&lt;p&gt;Skip this workflow if you already have locked-down CI as the only writer. Your agents should not push. Humans merge from CI green only.&lt;/p&gt;

&lt;p&gt;Skip it if the free server may touch production credentials. A scratch box is a scratch box. Do not launder secrets through it.&lt;/p&gt;

&lt;p&gt;Skip it if you need a legal archive. JSON in &lt;code&gt;/tmp&lt;/code&gt; is not a records system.&lt;/p&gt;

&lt;p&gt;And skip it if you wanted a model leaderboard. I did not provide one. I will not invent names or scores.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I want you to repeat instead
&lt;/h2&gt;

&lt;p&gt;Ask one question at logout. Can another machine fetch this SHA?&lt;/p&gt;

&lt;p&gt;If yes, you shipped a candidate. If no, you rented a story. Free model access does not change that physics. A free server does not change that physics.&lt;/p&gt;

&lt;p&gt;When a teammate pastes a chat ending in "all tests passed," ask for porcelain. Ask for &lt;code&gt;origin&lt;/code&gt;. Ask for the receipt file. That is the whole job.&lt;/p&gt;

&lt;p&gt;If you run the receipt script, paste the JSON. Do not paste the pep talk.&lt;/p&gt;

</description>
      <category>git</category>
      <category>testing</category>
      <category>ai</category>
      <category>devops</category>
    </item>
    <item>
      <title>FAQ: Five Myths About the Agent's Remote Shell</title>
      <dc:creator>Jordan Huang</dc:creator>
      <pubDate>Sun, 20 Sep 2026 04:26:49 +0000</pubDate>
      <link>https://dev.to/gitlab_3188/faq-five-myths-about-the-agents-remote-shell-25ha</link>
      <guid>https://dev.to/gitlab_3188/faq-five-myths-about-the-agents-remote-shell-25ha</guid>
      <description>&lt;p&gt;The agent said the remote shell was fine. Can you prove that claim with a file?&lt;/p&gt;

&lt;p&gt;It pasted a traceback, then a patch, then a shrug. I still needed a receipt from the box itself.&lt;/p&gt;

&lt;p&gt;This FAQ kills five claims developers repeat. They sound like caution. They are not.&lt;/p&gt;

&lt;p&gt;This is an identity problem, not a prompt problem. Which Python. Which clock. Which tree. Which exit.&lt;/p&gt;

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

&lt;p&gt;This is not a model bake-off. I will not name models.&lt;/p&gt;

&lt;p&gt;This is not a quota thread. I will not invent limits.&lt;/p&gt;

&lt;p&gt;This is not a speed story. I have no honest benchmark here.&lt;/p&gt;

&lt;p&gt;That JSON file is the only bar I trust.&lt;/p&gt;

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

&lt;p&gt;A free model plus a free server is one convenient place to collect the remote side of this receipt. The checklist still works on any throwaway shell you already own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 1: Remote Python is "the same Python"
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The claim
&lt;/h3&gt;

&lt;p&gt;"It is Python 3. The box is fine." Why does that sentence feel finished to reviewers?&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it spreads
&lt;/h3&gt;

&lt;p&gt;The prompt said &lt;code&gt;python3&lt;/code&gt;. The traceback looked familiar. One import worked.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the claim skips
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;python3&lt;/code&gt; is a name, not an identity. Distros ship different ABIs. SoABI can drift. The stdlib path can drift too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evidence I actually want
&lt;/h3&gt;

&lt;p&gt;Run this on the remote shell. Run it on your laptop. Then diff the files.&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: interpreter fingerprint, not a vibe
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&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="n"&gt;platform&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;sysconfig&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;exe&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;executable&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;resolve&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;executable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exe&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;platform&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;implementation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;python_implementation&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;soabi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sysconfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_config_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SOABI&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;json_module&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&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="nf"&gt;__import__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;__file__&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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="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;exe_sha256_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;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;exe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_bytes&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="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did the executable path match? Did SOABI match? Did the stdlib path match?&lt;/p&gt;

&lt;p&gt;If any of those drift, your "same traceback" is a coincidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Corrected mental model
&lt;/h3&gt;

&lt;p&gt;Treat &lt;code&gt;python3&lt;/code&gt; as an alias. Demand a fingerprint before you compare failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 2: &lt;code&gt;which python3&lt;/code&gt; proves the interpreter that ran
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The claim
&lt;/h3&gt;

&lt;p&gt;"&lt;code&gt;which&lt;/code&gt; returned &lt;code&gt;/usr/bin/python3&lt;/code&gt;. We are good." Are you sure that path was the live process?&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it spreads
&lt;/h3&gt;

&lt;p&gt;PATH feels like a contract. It is only a search order. Agents mix &lt;code&gt;python&lt;/code&gt;, &lt;code&gt;python3&lt;/code&gt;, &lt;code&gt;uv run&lt;/code&gt;, and venv hooks.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the claim skips
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;which&lt;/code&gt; cannot see a process that already exited. It cannot see argv either.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evidence I actually want
&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;# proposal: inspect the live shell, not a memory of PATH&lt;/span&gt;
ps &lt;span class="nt"&gt;-o&lt;/span&gt; pid,ppid,user,lstart,cmd &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$$&lt;/span&gt;
&lt;span class="nb"&gt;readlink&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /proc/&lt;span class="nv"&gt;$$&lt;/span&gt;/exe &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
tr&lt;/span&gt; &lt;span class="s1"&gt;'\0'&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; &amp;lt; /proc/&lt;span class="nv"&gt;$$&lt;/span&gt;/environ | egrep &lt;span class="s1"&gt;'^(PATH|VIRTUAL_ENV|PYTHON|PWD)='&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
type &lt;/span&gt;python python3
&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; python3
&lt;span class="nv"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; python3&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$bin&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$bin&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; shasum &lt;span class="nt"&gt;-a&lt;/span&gt; 256 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$bin&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
file &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$bin&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask for the exact argv next. Compare it to &lt;code&gt;history&lt;/code&gt; or &lt;code&gt;script&lt;/code&gt; output. If you cannot, you do not know.&lt;/p&gt;

&lt;h3&gt;
  
  
  Corrected mental model
&lt;/h3&gt;

&lt;p&gt;PATH is a rumor. Argv plus a hash is evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 3: The prompt said UTC, so the box used UTC
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The claim
&lt;/h3&gt;

&lt;p&gt;"We logged timestamps in UTC. The server is UTC." Who actually configured that clock on the box?&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it spreads
&lt;/h3&gt;

&lt;p&gt;Cloud screenshots look UTC. Dockerfiles set &lt;code&gt;TZ&lt;/code&gt;. People assume the next box follows.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the claim skips
&lt;/h3&gt;

&lt;p&gt;Locale, &lt;code&gt;TZ&lt;/code&gt;, and &lt;code&gt;/etc/localtime&lt;/code&gt; are three knobs. Agents rarely print all three. A "passed at 16:00" line can be local, UTC, or leftover state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evidence I actually want
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y-%m-%dT%H:%M:%SZ
&lt;span class="nb"&gt;date&lt;/span&gt; +%Y-%m-%dT%H:%M:%S%z
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 datetime, os, time
print("TZ", os.environ.get("TZ"))
print("tzname", time.tzname)
print("offset", datetime.datetime.now().astimezone().utcoffset())
print("aware_now", datetime.datetime.now().astimezone().isoformat())
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;timedatectl 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"timedatectl missing"&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /etc/localtime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare laptop versus remote. If offsets differ, your merged log is fiction.&lt;/p&gt;

&lt;p&gt;Labeled example, not a measured incident: both sides print &lt;code&gt;16:04 passed&lt;/code&gt;. One clock is PDT. One clock is UTC. That is a two-hour lie with matching strings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Corrected mental model
&lt;/h3&gt;

&lt;p&gt;Time is configuration. Print it on every run, both sides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 4: Exit code 0 belongs to the files you edited
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The claim
&lt;/h3&gt;

&lt;p&gt;"The remote command returned 0. Ship it." Returned zero against which git tree, exactly?&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it spreads
&lt;/h3&gt;

&lt;p&gt;Exit codes feel atomic. Agents narrate them with confidence. Chat wraps them in a bow.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the claim skips
&lt;/h3&gt;

&lt;p&gt;You may not know cwd. You may not know which clone. You may not know if the files match your editor. A green run against a stale tree is a green lie.&lt;/p&gt;

&lt;p&gt;This is not a test-quality FAQ. I already wrote that one. This is a tree-identity FAQ.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evidence I actually want
&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;# proposal: bind the later exit code to a tree fingerprint&lt;/span&gt;
git rev-parse HEAD
git status &lt;span class="nt"&gt;--porcelain&lt;/span&gt;
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt;
&lt;span class="nb"&gt;pwd
&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;'
import hashlib, json, pathlib
root = pathlib.Path(".").resolve()
files = sorted(
    p for p in root.rglob("*")
    if p.is_file()
    and ".git" not in p.parts
    and "node_modules" not in p.parts
    and p.stat().st_size &amp;lt;= 1_000_000
)
h = hashlib.sha256()
for p in files:
    h.update(p.relative_to(root).as_posix().encode() + b"&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="sh"&gt;")
    h.update(p.read_bytes())
print(json.dumps({
    "cwd": str(root),
    "file_count": len(files),
    "tree_sha256": h.hexdigest(),
}, indent=2))
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"now run your command"&lt;/span&gt;
&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;Keep HEAD, the hash, and the exit in one artifact. If HEAD moved, the exit is about another world. If the tree hash moved, stop quoting it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Corrected mental model
&lt;/h3&gt;

&lt;p&gt;An exit code has no meaning without a tree hash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 5: The free box is your audit log
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The claim
&lt;/h3&gt;

&lt;p&gt;"The server kept the session. We can replay it." Can you still replay it after the tab closes?&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it spreads
&lt;/h3&gt;

&lt;p&gt;SSH feels permanent. Cloud shells feel like machines. Chat transcripts feel like journals.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the claim skips
&lt;/h3&gt;

&lt;p&gt;A free server is a convenience. It is not an evidence locker. I will not claim retention. I will not claim disk size. I will not claim uptime.&lt;/p&gt;

&lt;p&gt;If you did not copy the receipt off the box, you do not have it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evidence I actually want
&lt;/h3&gt;

&lt;p&gt;Write one JSON file. Leave with that file. Paste it into git if the run matters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./receipt.sh /tmp/remote-receipt.json
&lt;span class="c"&gt;# then copy that file off the box before you close anything&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Corrected mental model
&lt;/h3&gt;

&lt;p&gt;Logs you do not export did not happen. Chat is not storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The artifact: &lt;code&gt;receipt.sh&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is the whole method. Remote once. Local once. Diff the JSON.&lt;/p&gt;

&lt;p&gt;Treat the script as a proposal. I am not attaching a public dataset. I am not inventing a pass rate.&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.sh — proposal, not a product claim&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;out&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="p"&gt;./shell-receipt.json&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

python3 - &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="s2"&gt;"&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;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import hashlib, json, os, platform, socket, subprocess, sys, sysconfig, time
from datetime import datetime, timezone
from pathlib import Path

def sh(cmd):
    try:
        p = subprocess.run(
            cmd, shell=True, capture_output=True, text=True, timeout=20
        )
        return {
            "cmd": cmd,
            "code": p.returncode,
            "stdout": p.stdout.strip(),
            "stderr": p.stderr.strip()[:400],
        }
    except Exception as e:
        return {"cmd": cmd, "error": str(e)}

root = Path.cwd().resolve()
exe = Path(sys.executable).resolve()
tree = hashlib.sha256()
count = 0
for p in sorted(root.rglob("*")):
    if not p.is_file():
        continue
    if ".git" in p.parts or "node_modules" in p.parts:
        continue
    if p.stat().st_size &amp;gt; 1_000_000:
        continue
    tree.update(p.relative_to(root).as_posix().encode() + b"&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="sh"&gt;")
    tree.update(p.read_bytes())
    count += 1

receipt = {
    "schema": "shell-receipt/v1",
    "utc": datetime.now(timezone.utc).isoformat(),
    "host": socket.gethostname(),
    "platform": platform.platform(),
    "uname": sh("uname -a"),
    "cwd": str(root),
    "uid": getattr(os, "getuid", lambda: None)(),
    "tz": os.environ.get("TZ"),
    "tzname": time.tzname,
    "python": {
        "executable": str(exe),
        "version": sys.version,
        "implementation": platform.python_implementation(),
        "soabi": sysconfig.get_config_var("SOABI"),
    },
    "path_python3": sh("command -v python3"),
    "git": {
        "head": sh("git rev-parse HEAD"),
        "status": sh("git status --porcelain"),
        "remote": sh("git remote -v"),
    },
    "tree": {"file_count": count, "sha256": tree.hexdigest()},
}

Path(sys.argv[1]).write_text(json.dumps(receipt, indent=2) + "&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;")
print("wrote", sys.argv[1])
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How I compare the two files
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x receipt.sh
./receipt.sh /tmp/remote.json
./receipt.sh /tmp/local.json
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
from pathlib import Path

a = json.loads(Path("/tmp/remote.json").read_text())
b = json.loads(Path("/tmp/local.json").read_text())
for k in ("platform", "python", "cwd", "tree", "tzname"):
    print(f"{k:10} match={a.get(k)==b.get(k)}")
if a["git"]["head"] != b["git"]["head"]:
    print("HEAD mismatch: stop sharing exit codes")
if a["tree"]["sha256"] != b["tree"]["sha256"]:
    print("tree mismatch: the remote exit is another world")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Decision table
&lt;/h2&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;If it matches&lt;/th&gt;
&lt;th&gt;If it does not&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Python fingerprint&lt;/td&gt;
&lt;td&gt;Tracebacks are comparable&lt;/td&gt;
&lt;td&gt;Do not paste remote stacks onto local code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;git&lt;/code&gt; HEAD&lt;/td&gt;
&lt;td&gt;You may discuss the exit&lt;/td&gt;
&lt;td&gt;Fetch or re-clone first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tree &lt;code&gt;sha256&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The command saw the same files&lt;/td&gt;
&lt;td&gt;The green bar is another tree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TZ offset&lt;/td&gt;
&lt;td&gt;You may merge logs&lt;/td&gt;
&lt;td&gt;Split the timelines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON copied off-box&lt;/td&gt;
&lt;td&gt;You have a receipt&lt;/td&gt;
&lt;td&gt;You have a story&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Print that table in the PR. Or refuse the PR. Those are the two options I respect.&lt;/p&gt;

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

&lt;p&gt;The script skips files over 1MB. That is a bias.&lt;/p&gt;

&lt;p&gt;It skips &lt;code&gt;node_modules&lt;/code&gt; on purpose. That is another bias.&lt;/p&gt;

&lt;p&gt;It does not freeze the network. A remote install can still race.&lt;/p&gt;

&lt;p&gt;It does not prove the agent used this interpreter. It proves the interpreter you invoked while collecting the receipt.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;timedatectl&lt;/code&gt; may be missing. That absence is data too.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/proc&lt;/code&gt; may be missing. Then record that gap in the JSON.&lt;/p&gt;

&lt;p&gt;A free server can disappear. Export that file before the session dies.&lt;/p&gt;

&lt;p&gt;Do not dump full &lt;code&gt;environ&lt;/code&gt; into git. Secrets hide in PATH-adjacent variables.&lt;/p&gt;

&lt;p&gt;This habit does not make a model "safe." It makes your claims checkable.&lt;/p&gt;

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

&lt;p&gt;Do not use a shared free server for customer data.&lt;/p&gt;

&lt;p&gt;Do not put private keys on that box.&lt;/p&gt;

&lt;p&gt;Do not treat it as CI if you need retention, IAM, or an artifact store.&lt;/p&gt;

&lt;p&gt;Do not use it when policy forbids unknown hosts.&lt;/p&gt;

&lt;p&gt;If you need those controls, use your real CI. Pay for isolation in your real pipeline instead. This FAQ is not that design.&lt;/p&gt;

&lt;h2&gt;
  
  
  The only question I ask now
&lt;/h2&gt;

&lt;p&gt;Where is the JSON for that remote claim?&lt;/p&gt;

&lt;p&gt;No JSON means I reject the remote claim. Run &lt;code&gt;receipt.sh&lt;/code&gt; on the throwaway box. Run it on the laptop. Diff the files.&lt;/p&gt;

&lt;p&gt;The agent can still be wrong. At least the box cannot hide behind a shrug.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>python</category>
      <category>devops</category>
    </item>
    <item>
      <title>FAQ: A Free Session Is Not a Build Receipt</title>
      <dc:creator>Jordan Huang</dc:creator>
      <pubDate>Sun, 20 Sep 2026 01:19:08 +0000</pubDate>
      <link>https://dev.to/gitlab_3188/faq-a-free-session-is-not-a-build-receipt-fe</link>
      <guid>https://dev.to/gitlab_3188/faq-a-free-session-is-not-a-build-receipt-fe</guid>
      <description>&lt;p&gt;Did that patch leave a host you do not own?&lt;br&gt;
I keep hearing five myths around complimentary coding boxes.&lt;/p&gt;

&lt;p&gt;This FAQ is a receipt checklist, not a vibe check.&lt;br&gt;
Would you merge a binary with no hash, ever?&lt;/p&gt;
&lt;h2&gt;
  
  
  What broke in my head
&lt;/h2&gt;

&lt;p&gt;Agents now jump onto a remote box without asking.&lt;br&gt;
The chat looks finished while the compile looks green.&lt;br&gt;
Then the real CI job explodes on a different host.&lt;/p&gt;

&lt;p&gt;Does that sequence feel uncomfortably familiar to you?&lt;br&gt;
I wanted one file I could commit beside the patch.&lt;/p&gt;
&lt;h2&gt;
  
  
  The mental model I want
&lt;/h2&gt;

&lt;p&gt;Treat every complimentary host as an untrusted compiler.&lt;br&gt;
Treat every free model turn as unlabeled text until tagged.&lt;/p&gt;

&lt;p&gt;The remote box is not your shipping laptop.&lt;br&gt;
The agent session is not your release pipeline.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 1: The complimentary host matches my laptop
&lt;/h2&gt;

&lt;p&gt;People repeat this claim like it is weather.&lt;br&gt;
They say it built there, so it builds here.&lt;/p&gt;

&lt;p&gt;Can you show uname, python3 --version, and which cc?&lt;br&gt;
Those three strings decide whether the claim survives.&lt;/p&gt;

&lt;p&gt;Linux userland is not the same as macOS userland.&lt;br&gt;
A container glibc is not your workstation glibc either.&lt;/p&gt;

&lt;p&gt;Compare fingerprints before you copy any built artifact.&lt;br&gt;
Without a fingerprint, you should refuse the copy.&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;uname&lt;/span&gt; &lt;span class="nt"&gt;-srm&lt;/span&gt;
python3 &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; cc &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; rustc &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
echo&lt;/span&gt; &lt;span class="s2"&gt;"PATH=&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that command block on the complimentary host.&lt;br&gt;
Run the same block on the machine that ships.&lt;/p&gt;

&lt;p&gt;Different strings mean you actually built different products.&lt;br&gt;
Do not argue with those platform strings later.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 2: Free model output needs no author tag
&lt;/h2&gt;

&lt;p&gt;I hear this claim in almost every review.&lt;br&gt;
People say the model is free, so skip tags.&lt;/p&gt;

&lt;p&gt;Does authorship vanish because nobody sent an invoice?&lt;br&gt;
A patch still entered your tree from somewhere unknown.&lt;/p&gt;

&lt;p&gt;Log the turn, the prompt hash, and the diff hash.&lt;br&gt;
You are logging provenance, not a marketing brand name.&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; .session
&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +&lt;span class="s2"&gt;"%Y-%m-%dT%H:%M:%SZ"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .session/started_at_utc.txt
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MODEL_LABEL&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;-free-model&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .session/model_label.txt
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .session/diff_stat.txt
git rev-parse HEAD &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .session/base_commit.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write unknown-free-model when the label is missing.&lt;br&gt;
An unknown label is honest, while a missing file is not.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;br&gt;
This walkthrough uses MonkeyCode as the example complimentary host.&lt;/p&gt;

&lt;p&gt;Free model access and a free server option are available there.&lt;br&gt;
I still refuse to treat that session as CI.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 3: A compiler on the box makes artifacts portable
&lt;/h2&gt;

&lt;p&gt;This myth still burns teams during release week.&lt;br&gt;
Someone copies a binary off the box and ships.&lt;/p&gt;

&lt;p&gt;Would you ship a binary you cannot rebuild today?&lt;br&gt;
Then you should not ship this copied binary either.&lt;/p&gt;

&lt;p&gt;The remote compile is only a smoke signal.&lt;br&gt;
Rebuild under your pinned toolchain, or do not ship.&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; .session
find dist &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-print0&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;sort&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="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .session/dist.sha256
&lt;span class="c"&gt;# rebuild on the ship host, then diff the two hash files&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Divergent hashes taught you something while the lesson was cheap.&lt;br&gt;
A missing hash taught you nothing about portability.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 4: No invoice means no paper trail
&lt;/h2&gt;

&lt;p&gt;Compliance folks hate this myth for good reason.&lt;br&gt;
Engineers repeat it because the dashboard said free.&lt;/p&gt;

&lt;p&gt;Free compute still executed secrets if you pasted them.&lt;br&gt;
Free compute still wrote files you might later commit.&lt;/p&gt;

&lt;p&gt;Demand a bill of materials before the victory lap.&lt;br&gt;
A session without that file is only a rumor.&lt;/p&gt;

&lt;p&gt;I want one JSON file sitting beside the patch.&lt;br&gt;
The next human should re-check facts, not feelings.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 5: Copying files back equals a pipeline
&lt;/h2&gt;

&lt;p&gt;The agent says done and dumps a tarball on disk.&lt;br&gt;
You extract it over src and open the merge request.&lt;/p&gt;

&lt;p&gt;Is a file copy with extra confidence a pipeline?&lt;br&gt;
I do not treat that copy as a release builder.&lt;/p&gt;

&lt;p&gt;Remote work stays a draft until your pipeline rebuilds it.&lt;br&gt;
Your pipeline is the only builder you should trust.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status &lt;span class="nt"&gt;--porcelain&lt;/span&gt;
git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt;
&lt;span class="c"&gt;# stop if unexpected lockfiles or binaries appear&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unexpected lockfiles are a smell worth stopping for.&lt;br&gt;
Unexpected binaries are a hard stop, not a debate.&lt;/p&gt;
&lt;h2&gt;
  
  
  PATH lies more than people think
&lt;/h2&gt;

&lt;p&gt;The complimentary host PATH often includes mystery toolchains.&lt;br&gt;
Your laptop PATH includes another set of mystery toolchains.&lt;/p&gt;

&lt;p&gt;Never trust a binary name without command -v plus a hash.&lt;br&gt;
A pretty interpreter name is not a receipt by itself.&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;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; python3
python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sys; print(sys.executable)"&lt;/span&gt;
&lt;span class="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; python3&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;That hash is the interpreter you actually invoked.&lt;br&gt;
A pretty python3 name on PATH is not evidence.&lt;/p&gt;
&lt;h2&gt;
  
  
  Artifact: a session bill of materials
&lt;/h2&gt;

&lt;p&gt;Here is a small Python script you can copy.&lt;br&gt;
Treat it as a proposal and run it yourself.&lt;/p&gt;

&lt;p&gt;Save the file as &lt;code&gt;session_bom.py&lt;/code&gt; in the repo root.&lt;br&gt;
Run it on the complimentary host before copying files.&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;Write .session/bom.json for a complimentary coding host.&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;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;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;platform&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="n"&gt;ROOT&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;cwd&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;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;.session&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;bom.json&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;args&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;str&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;p&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;False&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;except&lt;/span&gt; &lt;span class="nb"&gt;FileNotFoundError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&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;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&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;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;sha256_file&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;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="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="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;rb&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;f&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;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;65536&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&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;h&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;pip_freeze_hash&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="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;text&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="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-m&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;pip&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;freeze&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;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;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;text&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="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;git_info&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;head&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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="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="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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_email_set&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="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;config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user.email&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="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tracked&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;rel&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;src&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;app&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;lib&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ROOT&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;rel&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;d&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="k"&gt;continue&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="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rglob&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="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="nf"&gt;is_file&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;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;st_size&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;tracked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&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="nf"&gt;relative_to&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sha256_file&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="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;bom&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;generated_at_utc&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;hostname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;node&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;platform&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;executable&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="n"&gt;executable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cwd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path_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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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;PATH&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="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pip_freeze_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;pip_freeze_hash&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="nf"&gt;git_info&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_label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MODEL_LABEL&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;unknown-free-model&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;tracked_files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tracked&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;OUT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;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;OUT&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;bom&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="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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wrote &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;OUT&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="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;Export a model label even when the name is unknown.&lt;br&gt;
Then copy &lt;code&gt;.session/bom.json&lt;/code&gt; along with 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;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MODEL_LABEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"unknown-free-model"&lt;/span&gt;
python3 session_bom.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The merge request now carries a receipt, not a story.&lt;br&gt;
Reviewers can argue with files instead of chat memory.&lt;/p&gt;
&lt;h2&gt;
  
  
  A test that refuses a naked patch
&lt;/h2&gt;

&lt;p&gt;This pytest file fails closed on purpose.&lt;br&gt;
A missing BOM means the patch should not pass.&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;# test_session_bom.py — proposal, not a production suite
&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_session_bom_exists_and_names_a_host&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;p&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;.session/bom.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;p&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing .session/bom.json; refuse the patch&lt;/span&gt;&lt;span class="sh"&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;p&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;assert&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;hostname&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;BOM hostname is empty&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;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;python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BOM python is empty&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;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;git&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;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;head&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;BOM has no git head&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;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;model_label&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;BOM has no model label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_session_bom_is_not_your_laptop_claim&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.session/bom.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;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="c1"&gt;# Fill this from YOUR ship host, not from memory.
&lt;/span&gt;    &lt;span class="n"&gt;ship_platform_needle&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_ME&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;ship_platform_needle&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_ME&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;set the ship host needle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;remote_platform=&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="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;platform&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;Replace &lt;code&gt;REPLACE_ME&lt;/code&gt; with a substring from your ship host.&lt;br&gt;
A green test must prove the platforms match, or rebuild.&lt;/p&gt;
&lt;h2&gt;
  
  
  Diff the BOMs, then approve
&lt;/h2&gt;

&lt;p&gt;I also diff two BOM files before I approve.&lt;br&gt;
Hostname, platform, python, and pip freeze hash must be visible.&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;# diff_bom.py — proposal: print mismatched BOM keys
&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="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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;2&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;keys&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;hostname&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;platform&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;python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pip_freeze_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;model_label&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;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;keys&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;a&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;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="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;MISMATCH &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;a&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;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s"&gt; vs &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 diff_bom.py .session/bom.json ship_host_bom.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any MISMATCH line means rebuild on the ship host.&lt;br&gt;
Do not negotiate with a mismatched interpreter hash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision table I actually use
&lt;/h2&gt;

&lt;p&gt;I keep this table next to the merge request template.&lt;br&gt;
Ask the author which row they claim to occupy.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claim you hear&lt;/th&gt;
&lt;th&gt;Evidence to demand&lt;/th&gt;
&lt;th&gt;If missing, do this&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;It built on the free box&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;platform&lt;/code&gt; plus compiler path in the BOM&lt;/td&gt;
&lt;td&gt;Rebuild on the ship host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The model wrote a small patch&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;model_label&lt;/code&gt; plus &lt;code&gt;git diff --stat&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Mark author unknown and review harder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Just copy dist/&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;dist.sha256&lt;/code&gt; versus a local rebuild&lt;/td&gt;
&lt;td&gt;Throw the copy away&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nothing sensitive happened&lt;/td&gt;
&lt;td&gt;a command history you actually control&lt;/td&gt;
&lt;td&gt;Rotate anything you pasted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI will catch it&lt;/td&gt;
&lt;td&gt;CI uses the same pins you named&lt;/td&gt;
&lt;td&gt;Diff the pins; do not guess&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each row is a claim, evidence, and a refusal action.&lt;br&gt;
If evidence is missing, take the refusal action immediately.&lt;/p&gt;

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

&lt;p&gt;A BOM is not a security audit of the host.&lt;br&gt;
A BOM is not a license scan of the tree.&lt;/p&gt;

&lt;p&gt;It will not catch a model that lied in comments.&lt;br&gt;
It will not freeze a moving complimentary image either.&lt;/p&gt;

&lt;p&gt;A complimentary image can change under you without notice.&lt;br&gt;
I do not claim uptime, hardware, quotas, or model names.&lt;/p&gt;

&lt;p&gt;If you need those numbers, read product docs that day.&lt;br&gt;
Do not copy capacity claims from a blog post later.&lt;/p&gt;

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

&lt;p&gt;Skip this if you already have a locked remote builder.&lt;br&gt;
Skip this if policy forbids any complimentary host usage.&lt;/p&gt;

&lt;p&gt;Skip this if you cannot write &lt;code&gt;.session&lt;/code&gt; into the repo.&lt;br&gt;
Skip this if your threat model needs attested hardware.&lt;/p&gt;

&lt;p&gt;This workflow is for messy agent sessions on scratch boxes.&lt;br&gt;
It is not a substitute for a real compliance program.&lt;/p&gt;

&lt;h2&gt;
  
  
  The questions I ask before merge
&lt;/h2&gt;

&lt;p&gt;I ask five questions before I approve the merge.&lt;br&gt;
Each one needs a file, not a shrug.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which host produced these bytes, according to the BOM?&lt;/li&gt;
&lt;li&gt;Which model label sits inside that same JSON file?&lt;/li&gt;
&lt;li&gt;Did I rebuild the tree under the real ship pins?&lt;/li&gt;
&lt;li&gt;Did unexpected binaries land inside the git diff?&lt;/li&gt;
&lt;li&gt;What secret might have hit that complimentary box?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any answer is a shrug, I block the merge.&lt;br&gt;
A shrug from a free session is not a receipt.&lt;/p&gt;

&lt;p&gt;Would you accept a shrug from a human intern today?&lt;br&gt;
Then do not accept that shrug from a complimentary session.&lt;/p&gt;

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

&lt;p&gt;Complimentary compute remains useful for drafts and spikes.&lt;br&gt;
Unlabeled compute is how rumors sneak into main.&lt;/p&gt;

&lt;p&gt;Write the BOM before you copy a single artifact.&lt;br&gt;
Hash the outputs, then rebuild where you actually ship.&lt;/p&gt;

&lt;p&gt;Which of these five myths are you still repeating tomorrow?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>python</category>
      <category>testing</category>
    </item>
    <item>
      <title>FAQ: Five Myths About Promoting Code Off a Free Agent Box</title>
      <dc:creator>Jordan Huang</dc:creator>
      <pubDate>Sat, 19 Sep 2026 22:11:12 +0000</pubDate>
      <link>https://dev.to/gitlab_3188/faq-five-myths-about-promoting-code-off-a-free-agent-box-1o73</link>
      <guid>https://dev.to/gitlab_3188/faq-five-myths-about-promoting-code-off-a-free-agent-box-1o73</guid>
      <description>&lt;p&gt;Did the free box just become your origin?&lt;/p&gt;

&lt;p&gt;I keep catching that assumption in review. A remote agent workspace is a draft. It is not a release channel.&lt;/p&gt;

&lt;p&gt;This FAQ is about promotion, not prompting. You drafted on a cheap remote box. Fine. Then someone treats that box like CI. That is the bug.&lt;/p&gt;

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

&lt;p&gt;I already wrote about empty scratch hosts. I already wrote about chat logs versus &lt;code&gt;git status&lt;/code&gt;. This one is narrower.&lt;/p&gt;

&lt;p&gt;How do you move bytes from a free agent box into a real repo without lying?&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a free box actually helps
&lt;/h2&gt;

&lt;p&gt;I still use a throwaway remote shell for first drafts.&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 both as a scratchpad with a network. I do not treat them as production. I do not treat them as CI.&lt;/p&gt;

&lt;p&gt;The value is cheap iteration. The risk is a fake handoff. The rest of this post is the handoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five myths
&lt;/h2&gt;

&lt;p&gt;Each myth has a claim, a check, and a better mental model. None of these need a vendor benchmark. They need hashes, lockfiles, and a local command.&lt;/p&gt;

&lt;h3&gt;
  
  
  Myth 1: The transcript file is the whole file
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; "I copied the function from chat. Ship it."&lt;/p&gt;

&lt;p&gt;Did the model truncate the tail? Did the UI fold imports? Chat is a viewport. It is not a filesystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check I actually run:&lt;/strong&gt;&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;# Label: proposed local check, not a production metric.&lt;/span&gt;
&lt;span class="nb"&gt;sha256sum &lt;/span&gt;src/billing/quote.py
&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; src/billing/quote.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the remote box, write the same two numbers into a manifest. If either number drifts, you copied a window. You did not copy a file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corrected model:&lt;/strong&gt; Promote paths, not bubbles. Hash the path. Then copy. Then hash again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Myth 2: The free box runtime is your runtime
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; "It imported on the agent server. We are good."&lt;/p&gt;

&lt;p&gt;Which Python? Which glibc? Which Node? A free server is one image. Your laptop is another. CI is a third.&lt;/p&gt;

&lt;p&gt;I ask three questions before I trust a remote green run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What does &lt;code&gt;python -V&lt;/code&gt; or &lt;code&gt;node -v&lt;/code&gt; print there?&lt;/li&gt;
&lt;li&gt;What does it print here?&lt;/li&gt;
&lt;li&gt;What does CI pin in the workflow file?
&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="c"&gt;# Label: collect facts. Do not invent a match.&lt;/span&gt;
python &lt;span class="nt"&gt;-V&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; which python
node &lt;span class="nt"&gt;-v&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
cat&lt;/span&gt; .python-version 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
cat&lt;/span&gt; .nvmrc 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Corrected model:&lt;/strong&gt; A remote interpreter is a guest. Guest success is a clue. It is not a contract.&lt;/p&gt;

&lt;h3&gt;
  
  
  Myth 3: A remote install gave you a trustworthy lockfile
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; "The agent ran install. The lockfile is current."&lt;/p&gt;

&lt;p&gt;Did it? Or did it mutate &lt;code&gt;package.json&lt;/code&gt; and skip the lock? Or rewrite &lt;code&gt;poetry.lock&lt;/code&gt; with a different resolver?&lt;/p&gt;

&lt;p&gt;Green install on a throwaway box is not a pin. I already beat that drum for local installs. The remote case is worse. You cannot see the resolver logs unless you kept them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check:&lt;/strong&gt;&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;--stat&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; lockfile.txt poetry.lock &lt;span class="se"&gt;\&lt;/span&gt;
  package-lock.json pnpm-lock.yaml Cargo.lock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the lockfile is missing, stop. If it changed without a reason, stop. If the agent "helpfully" regenerated it, treat that as a new change. Review it like code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corrected model:&lt;/strong&gt; The lockfile is the artifact. The install log is commentary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Myth 4: Remote green means skip the local smoke
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; "Tests already passed on the box. Why rerun?"&lt;/p&gt;

&lt;p&gt;Wrong machine. Wrong caches. Wrong secrets. Wrong working directory.&lt;/p&gt;

&lt;p&gt;This is not the old "did the agent really run tests" question. Assume they ran. They still ran over there.&lt;/p&gt;

&lt;p&gt;I want one boring command on my checkout. Not a full suite. A smoke that proves the files I copied are the files I think I copied.&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;# Label: example smoke only. Swap in your real target.&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; compileall src/billing/quote.py
pytest &lt;span class="nt"&gt;-q&lt;/span&gt; tests/test_quote.py &lt;span class="nt"&gt;-k&lt;/span&gt; parse &lt;span class="nt"&gt;--maxfail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that fails locally, the remote green build was a tourist photo. Nice lighting. Wrong city.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corrected model:&lt;/strong&gt; Remote tests answer "could this work." Local smoke answers "did this land."&lt;/p&gt;

&lt;h3&gt;
  
  
  Myth 5: A workspace link is a pull request
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; "I will just share the free server URL."&lt;/p&gt;

&lt;p&gt;With whom? For how long? With which secrets still in the env?&lt;/p&gt;

&lt;p&gt;A workspace link is not review. It has no diff. It has no CI. It has no CODEOWNERS. It often has your paste buffer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check before you paste a URL:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is &lt;code&gt;.env&lt;/code&gt; in the tree?&lt;/li&gt;
&lt;li&gt;Did the agent echo tokens in a log?&lt;/li&gt;
&lt;li&gt;Can a stranger fetch the workspace?&lt;/li&gt;
&lt;li&gt;Do you even own the retention story?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I do not know your provider's retention. I refuse to guess. If you cannot answer retention, do not share the box. Open a PR from your repo instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corrected model:&lt;/strong&gt; Git is the handoff protocol. HTTP to a scratch VM is a demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Artifact: a promotion manifest
&lt;/h2&gt;

&lt;p&gt;I use a tiny two-file protocol. No special platform. No claimed speedup.&lt;/p&gt;

&lt;p&gt;File one lives on the remote box: &lt;code&gt;promo.manifest&lt;/code&gt;. File two is this local checker. The checker is a proposal. Run it on a throwaway branch first.&lt;/p&gt;

&lt;p&gt;Remote side, after the agent stops 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;# Label: proposed remote snapshot.&lt;/span&gt;
&lt;span class="c"&gt;# Run inside the project root on the free box.&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;"# promo.manifest"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"pwd &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&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;"head &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--short&lt;/span&gt; HEAD 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;nogit&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;python &lt;span class="nt"&gt;-V&lt;/span&gt; 2&amp;gt;&amp;amp;1&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;"node &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;node &lt;span class="nt"&gt;-v&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;none&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  git ls-files &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="nt"&gt;--exclude-standard&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; f&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="o"&gt;[&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;$f&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="k"&gt;continue
    &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s  %s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt;&lt;span class="si"&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;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; promo.manifest
&lt;span class="nb"&gt;cat &lt;/span&gt;promo.manifest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy &lt;code&gt;promo.manifest&lt;/code&gt; home with the files. Not through chat if the tree is large. Use &lt;code&gt;scp&lt;/code&gt;, a bundle, or a patch.&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;# Label: proposed local verifier. Save as scripts/check_promo.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="nv"&gt;manifest&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;:?usage:&lt;span class="p"&gt; check_promo.sh promo.manifest&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nb"&gt;hash &lt;/span&gt;path&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$hash&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="se"&gt;\#&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;head&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;python|node&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;esac&lt;/span&gt;
  &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; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$path&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;"MISSING &lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
    &lt;span class="k"&gt;continue
  fi
  &lt;/span&gt;&lt;span class="nv"&gt;got&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt;&lt;span class="si"&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="nv"&gt;$got&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$hash&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;"DRIFT &lt;/span&gt;&lt;span class="nv"&gt;$path&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;"  remote &lt;/span&gt;&lt;span class="nv"&gt;$hash&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;"  local  &lt;/span&gt;&lt;span class="nv"&gt;$got&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
  &lt;span class="k"&gt;fi
done&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$manifest&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="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; package-lock.json &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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; pnpm-lock.yaml &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="o"&gt;&amp;amp;&amp;amp;&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; poetry.lock &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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; Cargo.lock &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;"WARN no lockfile in this snapshot"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$fail&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the smoke you already trust:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash scripts/check_promo.sh ./promo.manifest
git status &lt;span class="nt"&gt;--short&lt;/span&gt;
&lt;span class="c"&gt;# only then: your local smoke command&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decision table I keep next to the script:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hash match, lockfile unchanged, smoke green → open the PR from &lt;strong&gt;this&lt;/strong&gt; repo.&lt;/li&gt;
&lt;li&gt;Hash match, lockfile changed → review the lockfile as its own diff.&lt;/li&gt;
&lt;li&gt;Hash drift → stop copying from chat. Recopy the path.&lt;/li&gt;
&lt;li&gt;Smoke red → the remote green build does not travel. Fix locally.&lt;/li&gt;
&lt;li&gt;No manifest → you do not have a promotion. You have a story.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This protocol does not prove correctness. It proves the bytes moved. It does not pin a model. It does not pin a server image. It does not replace CI.&lt;/p&gt;

&lt;p&gt;Hashes ignore file modes on some copies. Newlines can bite you. Windows versus Unix will lie. If you copy through Slack, you already lost.&lt;/p&gt;

&lt;p&gt;Who should not use this?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Anyone shipping secrets that already touched the free box.&lt;/li&gt;
&lt;li&gt;Anyone who needs a long-lived remote environment. I did not claim one.&lt;/li&gt;
&lt;li&gt;Anyone without a real git remote they control.&lt;/li&gt;
&lt;li&gt;Regulated work that forbids unknown hosted shells.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If MonkeyCode's free server is your draft box, export the manifest before you copy anything home. That is the whole ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mental model I want stuck in your head
&lt;/h2&gt;

&lt;p&gt;Ask four questions, in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which machine ran the command?&lt;/li&gt;
&lt;li&gt;Which bytes am I about to commit?&lt;/li&gt;
&lt;li&gt;Which lockfile travels with those bytes?&lt;/li&gt;
&lt;li&gt;Which system will rerun the check besides me?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you cannot answer all four, you are not promoting. You are hoping.&lt;/p&gt;

&lt;p&gt;A free model can draft. A free server can compile. Your repo still has to receive the patch. CI still has to hate it in public.&lt;/p&gt;

&lt;p&gt;That split is the job. Keep it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>git</category>
      <category>testing</category>
      <category>devops</category>
    </item>
    <item>
      <title>FAQ: What a Free Agent Session Does Not Prove</title>
      <dc:creator>Jordan Huang</dc:creator>
      <pubDate>Sat, 19 Sep 2026 18:59:18 +0000</pubDate>
      <link>https://dev.to/gitlab_3188/faq-what-a-free-agent-session-does-not-prove-5e7</link>
      <guid>https://dev.to/gitlab_3188/faq-what-a-free-agent-session-does-not-prove-5e7</guid>
      <description>&lt;p&gt;Have you ever copied a patch from a free agent box?&lt;br&gt;
Then watched local CI reject the same tree?&lt;/p&gt;

&lt;p&gt;I keep hearing five claims in review threads.&lt;br&gt;
They sound harmless until a merge fails locally.&lt;/p&gt;

&lt;p&gt;This FAQ treats those claims as testable myths.&lt;br&gt;
A free session can draft work at useful speed.&lt;br&gt;
It still cannot freeze your actual toolchain.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I mean by a free session
&lt;/h2&gt;

&lt;p&gt;Some agents give free model access for drafting patches.&lt;br&gt;
Some also give a free server to run shell commands.&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 use MonkeyCode's free models and free server as scratch.&lt;br&gt;
They help me try commands I might later keep.&lt;br&gt;
They do not pin a compiler, image, or locale.&lt;/p&gt;

&lt;p&gt;The rest of this piece stays useful without that product.&lt;br&gt;
The receipt workflow is just git, a shell, and honesty.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to read each answer
&lt;/h2&gt;

&lt;p&gt;Each myth has a claim, a check, and a better model.&lt;br&gt;
Skip anything you cannot verify on your own machine.&lt;/p&gt;

&lt;p&gt;Ask one rude question after every remote green build.&lt;br&gt;
What evidence will still exist when that box is gone?&lt;/p&gt;
&lt;h2&gt;
  
  
  FAQ 1: Remote green means the PR is merge-ready?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; The free server built it, so the PR is done.&lt;/p&gt;

&lt;p&gt;Did the remote compiler match your CI image?&lt;br&gt;
Did it use the same libc, locale, and umask?&lt;br&gt;
You probably do not know, and that is the problem.&lt;/p&gt;

&lt;p&gt;I run this after every copy, before any review ping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rev-parse HEAD
git status &lt;span class="nt"&gt;--porcelain&lt;/span&gt;
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I rerun the same command CI will run.&lt;br&gt;
A remote green build is a hint, not a gate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better model:&lt;/strong&gt; Treat remote green as an untrusted preview.&lt;br&gt;
Merge only after the local runner or CI repeats it.&lt;/p&gt;

&lt;p&gt;Quick list I keep next to the laptop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The git HEAD matches the PR branch exactly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git status --porcelain&lt;/code&gt; stays empty after the copy.&lt;/li&gt;
&lt;li&gt;One CI command runs locally and exits zero.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  FAQ 2: It was a free draft, so skip the model note?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; It was only a free model. Logging is ceremony.&lt;/p&gt;

&lt;p&gt;Which turn produced the patch you are about to commit?&lt;br&gt;
If you cannot point to it, you cannot replay advice.&lt;br&gt;
Free does not mean interchangeable across days or teammates.&lt;br&gt;
It means the draft was unpaid, not that it was pinned.&lt;/p&gt;

&lt;p&gt;I do not invent model names I cannot see in logs.&lt;br&gt;
I still write a one-line note in the receipt file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;session_note: drafted on a free model; not a version pin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why bother, if the name is not a hash?&lt;br&gt;
Because future you will ask where the patch was born.&lt;br&gt;
A note saying "free draft" beats a silent blob every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better model:&lt;/strong&gt; Record that the source was a free draft.&lt;br&gt;
Never pretend the model string is a compiler hash.&lt;/p&gt;
&lt;h2&gt;
  
  
  FAQ 3: Copying files off the box equals a commit?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; The files sit on the server, so they are saved.&lt;/p&gt;

&lt;p&gt;Saved where, on whose disk, and under which umask?&lt;br&gt;
A server path is not a branch you can push.&lt;br&gt;
It is a rental workspace with a short memory.&lt;/p&gt;

&lt;p&gt;I copy through a reviewed diff, never through hope.&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;# on your laptop, after scp or a download&lt;/span&gt;
git add &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
git diff
&lt;span class="c"&gt;# only then&lt;/span&gt;
git add &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you cannot &lt;code&gt;git add -p&lt;/code&gt; it, you do not own it yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better model:&lt;/strong&gt; Treat the free filesystem as a clipboard only.&lt;br&gt;
A commit is the first moment the work becomes yours.&lt;/p&gt;

&lt;p&gt;Questions I ask before I even open &lt;code&gt;scp&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which paths changed, and which paths must stay untouched?&lt;/li&gt;
&lt;li&gt;Did the agent rewrite lockfiles I never asked it to touch?&lt;/li&gt;
&lt;li&gt;Can I explain every hunk in one sitting?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  FAQ 4: Locale and timezone never leak into artifacts?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; The box is generic. Timestamps and sorts will match.&lt;/p&gt;

&lt;p&gt;Have you seen tests fail on filename sort order?&lt;br&gt;
Have you seen tarballs change because &lt;code&gt;tar&lt;/code&gt; saw local time?&lt;br&gt;
Have you seen Python tests break on &lt;code&gt;%c&lt;/code&gt; locale formats?&lt;/p&gt;

&lt;p&gt;I capture the personality of the box before I leave it.&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;date
date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;
locale
&lt;span class="nb"&gt;umask
id
&lt;/span&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'import locale,time,sys; print(sys.platform); print(locale.getlocale()); print(time.tzname)'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write those values into receipt.json and compare them at home.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better model:&lt;/strong&gt; The free server has a quiet personality of its own.&lt;br&gt;
Capture it, or you will debug ghosts after the copy.&lt;/p&gt;

&lt;p&gt;Leaks worth hunting in copied trees:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sorted test output can depend on &lt;code&gt;LC_COLLATE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Archive metadata can be stamped with the server timezone.&lt;/li&gt;
&lt;li&gt;Generated headers can embed local &lt;code&gt;strftime&lt;/code&gt; text.&lt;/li&gt;
&lt;li&gt;File modes may only make sense under that umask.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  FAQ 5: A teammate can resume from your memory?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; We share the same free product, so we share state.&lt;/p&gt;

&lt;p&gt;Do you share the same cwd and the same dirty tree?&lt;br&gt;
Do you share env names and the same uncommitted patch stack?&lt;br&gt;
Product sameness is not session sameness, not even close.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better model:&lt;/strong&gt; Hand off a receipt, not a remembered vibe.&lt;/p&gt;

&lt;p&gt;Minimum handoff packet I will accept from a teammate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep the &lt;code&gt;receipt.json&lt;/code&gt; captured on the remote box.&lt;/li&gt;
&lt;li&gt;Keep &lt;code&gt;receipt.json&lt;/code&gt; from the laptop after copy.&lt;/li&gt;
&lt;li&gt;Write the exact command that must pass locally.&lt;/li&gt;
&lt;li&gt;Attach a &lt;code&gt;git diff&lt;/code&gt; you can explain line by line.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No packet means no resume, because memory is not a worktree.&lt;/p&gt;
&lt;h2&gt;
  
  
  The artifact: two receipts and a diff
&lt;/h2&gt;

&lt;p&gt;I want a file I can attach to the PR.&lt;br&gt;
It records what the free session refused to freeze.&lt;/p&gt;

&lt;p&gt;Save the script below as &lt;code&gt;scripts/session-receipt.sh&lt;/code&gt;.&lt;br&gt;
Treat it as a labeled example, not a production service.&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;# session-receipt.sh — labeled example, run from a git worktree&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;OUT&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;receipt&lt;/span&gt;&lt;span class="p"&gt;.json&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;CMD_LOCAL&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;MUST_PASS_LOCALLY&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="p"&gt; -m pytest -q&lt;/span&gt;&lt;span class="k"&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; git rev-parse &lt;span class="nt"&gt;--is-inside-work-tree&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&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;"run this inside a git worktree"&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;python3 - &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CMD_LOCAL&lt;/span&gt;&lt;span class="s2"&gt;"&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;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import json, os, subprocess, sys, time, locale

out, cmd = sys.argv[1], sys.argv[2]

def sh(args):
    p = subprocess.run(args, capture_output=True, text=True)
    return p.returncode, p.stdout.strip(), p.stderr.strip()

_, head, _ = sh(["git", "rev-parse", "HEAD"])
_, status, _ = sh(["git", "status", "--porcelain"])
_, branch, _ = sh(["git", "branch", "--show-current"])
_, umask, _ = sh(["bash", "-lc", "umask"])
_, who, _ = sh(["id"])

receipt = {
    "captured_at_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
    "git_head": head,
    "git_branch": branch,
    "git_status_porcelain": status.splitlines(),
    "dirty": bool(status),
    "umask": umask,
    "id": who,
    "cwd": os.getcwd(),
    "locale": list(locale.getlocale()),
    "tzname": list(time.tzname),
    "python": sys.version.split()[0],
    "platform": sys.platform,
    "env_names_only": sorted(os.environ.keys()),
    "command_that_must_pass_locally": cmd,
    "model_note": "free-model draft; not a pin",
    "server_note": "free server is scratch; not CI",
}
with open(out, "w", encoding="utf-8") as f:
    json.dump(receipt, f, indent=2)
    f.write("&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;")
print("wrote", out)
if receipt["dirty"]:
    print("worktree is dirty; review git diff before you trust a copy")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it on the free server before you copy files.&lt;br&gt;
Run it again on your laptop after the copy lands.&lt;br&gt;
Then diff the two JSON files; that is the whole method.&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;chmod&lt;/span&gt; +x scripts/session-receipt.sh
&lt;span class="nv"&gt;MUST_PASS_LOCALLY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"python -m pytest -q"&lt;/span&gt; ./scripts/session-receipt.sh /tmp/remote-receipt.json
&lt;span class="c"&gt;# after copy&lt;/span&gt;
&lt;span class="nv"&gt;MUST_PASS_LOCALLY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"python -m pytest -q"&lt;/span&gt; ./scripts/session-receipt.sh /tmp/local-receipt.json
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
remote = json.load(open("/tmp/remote-receipt.json"))
local = json.load(open("/tmp/local-receipt.json"))
keys = ["python", "platform", "locale", "tzname", "umask", "git_head"]
for key in keys:
    same = remote.get(key) == local.get(key)
    print(key, "match=" + str(same), "remote=", remote.get(key), "local=", local.get(key))
if local.get("dirty"):
    print("LOCAL_DIRTY")
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;git_head&lt;/code&gt; matches and &lt;code&gt;dirty&lt;/code&gt; is false, the tree is honest.&lt;br&gt;
If locale or umask differ, inspect generated files with extra care.&lt;/p&gt;
&lt;h2&gt;
  
  
  Decision table
&lt;/h2&gt;

&lt;p&gt;Use this table before you click merge.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claim you heard&lt;/th&gt;
&lt;th&gt;Evidence that would support it&lt;/th&gt;
&lt;th&gt;What I do instead&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Remote build was green&lt;/td&gt;
&lt;td&gt;CI image digest matches the box&lt;/td&gt;
&lt;td&gt;Rerun the CI command locally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free model drafted it&lt;/td&gt;
&lt;td&gt;A written note that it was a draft&lt;/td&gt;
&lt;td&gt;Keep the note; do not pin a fake hash&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Files exist on the server&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;git add -p&lt;/code&gt; hunks you can explain&lt;/td&gt;
&lt;td&gt;Commit only explained hunks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Locale cannot matter&lt;/td&gt;
&lt;td&gt;Receipt locale equals laptop locale&lt;/td&gt;
&lt;td&gt;Diff receipts; inspect generated files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Teammate can continue&lt;/td&gt;
&lt;td&gt;Two receipts plus an explained diff&lt;/td&gt;
&lt;td&gt;Refuse a memory-only handoff&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the middle column twice before you argue.&lt;br&gt;
If you lack that evidence, you are holding a story.&lt;/p&gt;
&lt;h2&gt;
  
  
  What this receipt does not prove
&lt;/h2&gt;

&lt;p&gt;The receipt does not pin a model weight or vendor SKU.&lt;br&gt;
The receipt does not clone a CI container or a runner label.&lt;br&gt;
The receipt does not redact secrets hiding in env values.&lt;br&gt;
That is why I store env names only, never values.&lt;/p&gt;

&lt;p&gt;Free servers go away, and free models change without a changelog.&lt;br&gt;
I do not claim quotas, hardware, duration, or uptime here.&lt;br&gt;
Those numbers were not supplied, so they are not in this FAQ.&lt;/p&gt;
&lt;h2&gt;
  
  
  Who should not use this workflow
&lt;/h2&gt;

&lt;p&gt;Do not put customer data on a shared free server.&lt;br&gt;
Do not treat this script as a substitute for real CI.&lt;br&gt;
Do not use it if you need bit-for-bit model replay.&lt;br&gt;
Do not use it for regulated builds or release signing.&lt;/p&gt;

&lt;p&gt;Release engineers need pinned images and signed provenance.&lt;br&gt;
This FAQ is for people copying drafts off scratch boxes.&lt;/p&gt;

&lt;p&gt;If your threat model includes other tenants, stop here.&lt;br&gt;
A free server is a convenience, not a tenancy boundary.&lt;/p&gt;
&lt;h2&gt;
  
  
  A local gate I refuse to skip
&lt;/h2&gt;

&lt;p&gt;After the receipt diff looks boring, I still run 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;test&lt;/span&gt; &lt;span class="nt"&gt;-z&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="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"DIRTY TREE"&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;'
import json, os
cmd = json.load(open("/tmp/local-receipt.json"))["command_that_must_pass_locally"]
print("running:", cmd)
raise SystemExit(os.system(cmd))
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that command fails locally, the remote green was theater.&lt;br&gt;
I send the patch back and I do not merge on memory.&lt;/p&gt;

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

&lt;p&gt;So what did the free session actually prove?&lt;br&gt;
It proved a draft could run in one unpaid environment.&lt;br&gt;
That result is useful, but it is not a freeze.&lt;br&gt;
It is also not CI, no matter how green it looked.&lt;/p&gt;

&lt;p&gt;Export the receipt before you copy anything down.&lt;br&gt;
If you skip that step, you are merging a rumor.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>git</category>
      <category>testing</category>
      <category>devops</category>
    </item>
    <item>
      <title>FAQ: A Sandbox Build Is Not a Release Receipt</title>
      <dc:creator>Jordan Huang</dc:creator>
      <pubDate>Sat, 19 Sep 2026 15:50:33 +0000</pubDate>
      <link>https://dev.to/gitlab_3188/faq-a-sandbox-build-is-not-a-release-receipt-3jcn</link>
      <guid>https://dev.to/gitlab_3188/faq-a-sandbox-build-is-not-a-release-receipt-3jcn</guid>
      <description>&lt;p&gt;Did the agent compile your app on a scratch host?&lt;br&gt;
Did you almost treat that log as a release?&lt;br&gt;
I keep hearing both claims in pull requests.&lt;/p&gt;

&lt;p&gt;This FAQ kills five claims I still hear.&lt;br&gt;
Each claim dies with a command you already have.&lt;br&gt;
No dashboard screenshot counts as evidence here.&lt;/p&gt;
&lt;h2&gt;
  
  
  What this FAQ covers
&lt;/h2&gt;

&lt;p&gt;I am talking about code that only built remotely.&lt;br&gt;
I am not talking about model leaderboard drama.&lt;br&gt;
I am not replacing your real pipeline either.&lt;/p&gt;

&lt;p&gt;A scratch box is a compiler for ideas.&lt;br&gt;
A release is a hashed tree plus recorded commands.&lt;br&gt;
Those two objects are easy to confuse today.&lt;/p&gt;
&lt;h2&gt;
  
  
  The loop I actually trust
&lt;/h2&gt;

&lt;p&gt;I still let an agent thrash in a sandbox.&lt;br&gt;
Sometimes that sandbox is a free remote server.&lt;br&gt;
MonkeyCode is one place I use for that loop.&lt;/p&gt;

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

&lt;p&gt;It has free model access and a free server option.&lt;br&gt;
I still refuse to ship whatever that box compiled.&lt;br&gt;
The receipt workflow below works on any host.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 1: A sandbox compile is a release candidate
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; If it built there, it can ship.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What people repeat:&lt;/strong&gt; The compiler already blessed the tree.&lt;br&gt;
&lt;strong&gt;What that log proves:&lt;/strong&gt; Some compiler accepted some files.&lt;br&gt;
&lt;strong&gt;What it does not prove:&lt;/strong&gt; Your release toolchain accepts them.&lt;/p&gt;

&lt;p&gt;Ask one rude question before you tag anything.&lt;br&gt;
Which exact compiler binary produced that success line?&lt;br&gt;
If you cannot name it, you do not have a receipt.&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;# On the sandbox, after the agent shouts success:&lt;/span&gt;
which cc &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;cc &lt;span class="nt"&gt;--version&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;npm &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; node &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;python &lt;span class="nt"&gt;-V&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pip &lt;span class="nt"&gt;-V&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;go &lt;span class="nb"&gt;env &lt;/span&gt;GOVERSION GOTOOLCHAIN &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;git rev-parse HEAD
git status &lt;span class="nt"&gt;--porcelain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy those lines into the pull request body.&lt;br&gt;
If the agent cannot produce them, stop merging.&lt;br&gt;
A green sentence in chat is not a compiler identity.&lt;/p&gt;
&lt;h3&gt;
  
  
  Corrected mental model
&lt;/h3&gt;

&lt;p&gt;Treat the sandbox as a noisy preview compiler.&lt;br&gt;
Treat your runner as the only compiler that counts.&lt;br&gt;
Preview compilers do not get to mint versions.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 2: The sandbox toolchain matches production
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; It is Linux, so the versions are close enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What people repeat:&lt;/strong&gt; The container felt normal enough.&lt;br&gt;
&lt;strong&gt;What you can measure:&lt;/strong&gt; File names under &lt;code&gt;/usr&lt;/code&gt; lie often.&lt;br&gt;
&lt;strong&gt;What you still need:&lt;/strong&gt; A pinned toolchain file you own.&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;# Record what the sandbox actually is.&lt;/span&gt;
&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/os-release
&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; node &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; node &lt;span class="nt"&gt;-p&lt;/span&gt; process.version
&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; python3 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'import sys; print(sys.version)'&lt;/span&gt;
&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; rustc &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; rustc &lt;span class="nt"&gt;-V&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run the same block on your release image.&lt;br&gt;
Diff the two text files without mercy or hope.&lt;br&gt;
A missing minor version still breaks native addons.&lt;/p&gt;

&lt;p&gt;I keep a tiny allowlist, not a vibe check.&lt;br&gt;
If &lt;code&gt;node&lt;/code&gt; drifts, the receipt must fail closed.&lt;br&gt;
Close is boring. Boring is the point here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# toolchain.allowlist example
node==20.17.0
python==3.12.6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Corrected mental model
&lt;/h3&gt;

&lt;p&gt;OS family is not a toolchain pin.&lt;br&gt;
Pins live in files you review and hash.&lt;br&gt;
Everything else is tourism, not packaging.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 3: The agent saying "build succeeded" is an artifact
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; The model reported success, so the process did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What people repeat:&lt;/strong&gt; The last message looked official.&lt;br&gt;
&lt;strong&gt;What that string is:&lt;/strong&gt; Generated prose about a command.&lt;br&gt;
&lt;strong&gt;What an artifact is:&lt;/strong&gt; Bytes plus an exit code you stored.&lt;/p&gt;

&lt;p&gt;Would you accept a human saying "tests passed"?&lt;br&gt;
Would you skip JUnit because the standup sounded confident?&lt;br&gt;
Then do not accept a chat bubble as &lt;code&gt;exit 0&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;&lt;span class="c"&gt;# Wrap the real command. Ignore the model's summary.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; pipefail
./scripts/build.sh &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/build.stdout 2&amp;gt; /tmp/build.stderr
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/build.exit
&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /tmp/build.stdout /tmp/build.stderr
&lt;span class="nb"&gt;sha256sum&lt;/span&gt; /tmp/build.stdout /tmp/build.stderr /tmp/build.exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the exit file, not the assistant paragraph.&lt;br&gt;
If the wrapper never ran, you have theater.&lt;br&gt;
Theater does not belong in a release checklist.&lt;/p&gt;
&lt;h3&gt;
  
  
  Corrected mental model
&lt;/h3&gt;

&lt;p&gt;Summaries are commentary. Receipts are files.&lt;br&gt;
Commentary can be wrong and still sound calm.&lt;br&gt;
Files can be hashed. Calm cannot be hashed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 4: The UI file list is the working tree
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; The sidebar showed the patch, so disk matches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What people repeat:&lt;/strong&gt; I watched the diff render live.&lt;br&gt;
&lt;strong&gt;What a UI list is:&lt;/strong&gt; A view the tool chose to paint.&lt;br&gt;
&lt;strong&gt;What git cares about:&lt;/strong&gt; Blobs in the index and worktree.&lt;/p&gt;

&lt;p&gt;I do not argue with screenshots anymore.&lt;br&gt;
I ask for names, sizes, and hashes instead.&lt;br&gt;
If those three disagree, the UI lost.&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;--name-only&lt;/span&gt;
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt;
git ls-files &lt;span class="nt"&gt;-v&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'$1 ~ /[S?]/ {print}'&lt;/span&gt;
find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-not&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; &lt;span class="s1"&gt;'./.git/*'&lt;/span&gt; &lt;span class="nt"&gt;-print0&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sort&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="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/tree.sha256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then compare &lt;code&gt;/tmp/tree.sha256&lt;/code&gt; with your laptop copy.&lt;br&gt;
One missing file ends the debate immediately.&lt;br&gt;
Pretty file trees are not Merkle trees.&lt;/p&gt;
&lt;h3&gt;
  
  
  Corrected mental model
&lt;/h3&gt;

&lt;p&gt;The working tree is the product.&lt;br&gt;
The chat panel is a projector.&lt;br&gt;
Projectors do not ship.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 5: The same prompt rebuilds the same tree tomorrow
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; I can recreate this with the original request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What people repeat:&lt;/strong&gt; The prompt is the build script.&lt;br&gt;
&lt;strong&gt;What a prompt is:&lt;/strong&gt; A wish with unbounded inputs.&lt;br&gt;
&lt;strong&gt;What a rebuild needs:&lt;/strong&gt; Bytes, commands, and versions frozen.&lt;/p&gt;

&lt;p&gt;Prompts do not pin package indexes.&lt;br&gt;
Prompts do not pin compiler patches.&lt;br&gt;
Prompts do not pin the agent's extra tool calls.&lt;/p&gt;

&lt;p&gt;If you need the tree again, store the tree.&lt;br&gt;
Store the commands that mutated it too.&lt;br&gt;
Do not store a vibe and call it provenance.&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 &lt;span class="nt"&gt;-A&lt;/span&gt;
git diff &lt;span class="nt"&gt;--cached&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/agent.patch
&lt;span class="nb"&gt;sha256sum&lt;/span&gt; /tmp/agent.patch
&lt;span class="c"&gt;# Keep the patch. Throw away the prompt fan fiction.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Corrected mental model
&lt;/h3&gt;

&lt;p&gt;Reproducibility starts after the agent stops talking.&lt;br&gt;
Your job is to freeze the result, not the chat.&lt;br&gt;
Chat is grease. Grease is not a spec.&lt;/p&gt;
&lt;h2&gt;
  
  
  Artifact: a local release receipt
&lt;/h2&gt;

&lt;p&gt;I use one script after every sandbox session.&lt;br&gt;
It does not care which host wrote the files.&lt;br&gt;
It fails if you only have a story.&lt;/p&gt;

&lt;p&gt;Save this as &lt;code&gt;sandbox_receipt.py&lt;/code&gt; and run it locally.&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;Record a sandbox session as a hashed receipt.

Label: example script. Run it on a git checkout you control.
It does not talk to any vendor API.
&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;os&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;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&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;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="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;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;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;check&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;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&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="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="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&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;stderr&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="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;returncode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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;sha256_file&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;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="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="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;rb&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&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;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;65536&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&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;h&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;toolchain&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;keys&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;uname&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;uname&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;-a&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;node&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;node&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;-v&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;python&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;python3&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;-V&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;git&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;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;--version&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;found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;keys&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;code&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="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;code&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;else&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;code&lt;/span&gt;&lt;span class="si"&gt;}&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;found&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;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;code&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="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;HEAD&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;code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git diff failed; are you in a repo?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;extra_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;ls-files&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;--others&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;--exclude-standard&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;extra_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git ls-files failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;names&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="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;splitlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;extra&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;line&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;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="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;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;--build-cmd&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="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;--out&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;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;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;head_code&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;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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=v1&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;head_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="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;Need a git checkout you control.&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;files&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;hashed&lt;/span&gt; &lt;span class="o"&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="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;files&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;name&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="n"&gt;missing&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;name&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;hashed&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;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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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_file&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;st_size&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;build&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;skipped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exit&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;tail&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="k"&gt;if&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;build_cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bash&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;-lc&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="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;build_cmd&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;build&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;skipped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tail&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="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;:]}&lt;/span&gt;

    &lt;span class="n"&gt;receipt&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;cwd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getcwd&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_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;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;git_status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;status&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;toolchain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;toolchain&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;changed_file_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;changed_files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hashed&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_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;missing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;,&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;out&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;receipt&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 &lt;/span&gt;&lt;span class="si"&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;out&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; with &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hashed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; hashed files&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;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;missing paths:&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="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;3&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;hashed&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 changed files; nothing to receipt&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;4&lt;/span&gt;
    &lt;span class="k"&gt;if&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;build_cmd&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exit&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="mi"&gt;0&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;5&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;p&gt;Run it on the machine you actually own.&lt;br&gt;
Pull the patch first if the sandbox still holds it.&lt;br&gt;
Then force a local rebuild you can name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 sandbox_receipt.py &lt;span class="nt"&gt;--build-cmd&lt;/span&gt; &lt;span class="s1"&gt;'npm test --silent'&lt;/span&gt; &lt;span class="nt"&gt;--out&lt;/span&gt; receipt.json
python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'import json; print(json.load(open("receipt.json"))["build"])'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Attach &lt;code&gt;receipt.json&lt;/code&gt; to the pull request.&lt;br&gt;
Argue with the JSON, not the chat transcript.&lt;br&gt;
If the JSON is thin, the change is not ready.&lt;/p&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal you saw&lt;/th&gt;
&lt;th&gt;What it proves&lt;/th&gt;
&lt;th&gt;What it does not prove&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sandbox compile line&lt;/td&gt;
&lt;td&gt;A compiler on that box accepted a tree&lt;/td&gt;
&lt;td&gt;Your release compiler accepts it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent said success&lt;/td&gt;
&lt;td&gt;The model emitted that sentence&lt;/td&gt;
&lt;td&gt;A process exited zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI showed a file&lt;/td&gt;
&lt;td&gt;The tool rendered a path&lt;/td&gt;
&lt;td&gt;Bytes on disk match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free server still answers&lt;/td&gt;
&lt;td&gt;You can reach it today&lt;/td&gt;
&lt;td&gt;The host exists next week&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same prompt reused&lt;/td&gt;
&lt;td&gt;You asked again&lt;/td&gt;
&lt;td&gt;The same patch bytes return&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;receipt.json&lt;/code&gt; with hashes&lt;/td&gt;
&lt;td&gt;You froze a tree and a command&lt;/td&gt;
&lt;td&gt;Performance, security, or license clearance&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 template.&lt;br&gt;
It stops a surprising number of fake merges.&lt;br&gt;
People hate it, then they start using it.&lt;/p&gt;

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

&lt;p&gt;This receipt is not supply-chain provenance.&lt;br&gt;
It is not SLSA, not Sigstore, not a bill of materials.&lt;br&gt;
It will not catch a malicious registry swap.&lt;/p&gt;

&lt;p&gt;It also will not freeze a vanished free host.&lt;br&gt;
Do not keep secrets on a scratch server.&lt;br&gt;
Do not treat a deleted box as an audit log.&lt;/p&gt;

&lt;p&gt;Hashes of source are not hashes of images.&lt;br&gt;
A local &lt;code&gt;npm test&lt;/code&gt; is not production traffic.&lt;br&gt;
If you need bit-for-bit builds, use a real hermetic system.&lt;/p&gt;

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

&lt;p&gt;Skip this if you already have hermetic CI.&lt;br&gt;
You do not need another JSON souvenir then.&lt;br&gt;
Skip this if you cannot run git locally.&lt;/p&gt;

&lt;p&gt;Skip this for regulated binaries with named signers.&lt;br&gt;
A receipt script is not a signing ceremony.&lt;br&gt;
Skip this if the sandbox still holds production secrets.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I want in review
&lt;/h2&gt;

&lt;p&gt;I want three files, not a victory speech.&lt;br&gt;
I want the patch, the receipt, and the command log.&lt;br&gt;
I want the toolchain block to match the release image.&lt;/p&gt;

&lt;p&gt;Can the agent still help? Yes, in the sandbox.&lt;br&gt;
Can the sandbox mint a release? No, never.&lt;br&gt;
That split is the whole FAQ.&lt;/p&gt;

&lt;p&gt;Steal the script. Then argue with the JSON.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>git</category>
      <category>testing</category>
      <category>python</category>
    </item>
    <item>
      <title>Lock Collation Before You Merge a Generated Concat Step</title>
      <dc:creator>Jordan Huang</dc:creator>
      <pubDate>Sat, 19 Sep 2026 12:41:49 +0000</pubDate>
      <link>https://dev.to/gitlab_3188/lock-collation-before-you-merge-a-generated-concat-step-3j8e</link>
      <guid>https://dev.to/gitlab_3188/lock-collation-before-you-merge-a-generated-concat-step-3j8e</guid>
      <description>&lt;p&gt;The merge request is green on the author's laptop. CI is not.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bundle.json&lt;/code&gt; has the wrong checksum, and the job log still claims every fragment was included. The generated &lt;code&gt;pack.sh&lt;/code&gt; concatenates &lt;code&gt;fragments/*.json&lt;/code&gt;. That script came from an agent session on a remote Linux box. You are about to debug a flake that is not a flake.&lt;/p&gt;

&lt;p&gt;Collation did this. Path order is a build input. Treat it like one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the runner locale before you reread the chat
&lt;/h2&gt;

&lt;p&gt;Open the CI log first. Print the environment the job actually had, not the environment the agent described.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;locale
python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'import locale, sys; print(sys.platform); print(locale.getlocale())'&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"LANG=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LANG&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="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"LC_ALL=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LC_ALL&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="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"LC_COLLATE=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LC_COLLATE&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Print the same block on the machine that produced the patch. Do not argue from memory. &lt;code&gt;en_US.UTF-8&lt;/code&gt; and &lt;code&gt;C&lt;/code&gt; do not sort the same byte strings.&lt;/p&gt;

&lt;p&gt;A concat step that uses an unquoted glob is a sort step in disguise. The shell expands &lt;code&gt;fragments/*.json&lt;/code&gt; in collation order. Change the locale and you change the bundle. A checksum test then fails even though every file is present.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mixed machines make this look like CI noise
&lt;/h2&gt;

&lt;p&gt;You review on macOS. The agent wrote the script on Linux. Many hosted runners look closer to &lt;code&gt;C.UTF-8&lt;/code&gt; than to a desktop GUI locale. None of those hosts is broken. They are simply different inputs.&lt;/p&gt;

&lt;p&gt;Generated code makes the gap worse. The model copies a glob because the glob is short. Short is not deterministic. You want an explicit file list, or &lt;code&gt;LC_ALL=C&lt;/code&gt; plus a documented sort, or both.&lt;/p&gt;

&lt;p&gt;Names that differ only by case sit in the same bucket. Linux will keep &lt;code&gt;Auth.json&lt;/code&gt; and &lt;code&gt;auth.json&lt;/code&gt; as two files. Default macOS APFS may not. An agent on a Linux box can create both. Your laptop then reports one path, and CI reports two. That is not a flaky hasher. That is a filesystem contract bug sitting next to the collation bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recreate the mismatch in a throwaway directory
&lt;/h2&gt;

&lt;p&gt;Work somewhere disposable. Treat the next script as a demonstration template, not as a result you already measured.&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: demo_collation.sh — unexecuted template&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;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /tmp/collation-demo
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /tmp/collation-demo/fragments
&lt;span class="nb"&gt;cd&lt;/span&gt; /tmp/collation-demo

&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'{"k":1}\n'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; fragments/a.json
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'{"k":2}\n'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; fragments/B.json
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'{"k":3}\n'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; fragments/c.json

pack&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;out&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in &lt;/span&gt;fragments/&lt;span class="k"&gt;*&lt;/span&gt;.json&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;en_US.UTF-8 pack bundle-utf8.json &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C.UTF-8 pack bundle-utf8.json
&lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C pack bundle-c.json

&lt;span class="nb"&gt;sha256sum &lt;/span&gt;bundle-utf8.json bundle-c.json
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"desktop-like order:"&lt;/span&gt;
&lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;en_US.UTF-8 bash &lt;span class="nt"&gt;-lc&lt;/span&gt; &lt;span class="s1"&gt;'printf "%s\n" fragments/*.json'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
echo&lt;/span&gt; &lt;span class="s2"&gt;"C order:"&lt;/span&gt;
&lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C bash &lt;span class="nt"&gt;-lc&lt;/span&gt; &lt;span class="s1"&gt;'printf "%s\n" fragments/*.json'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it where both locales exist. If &lt;code&gt;locale -a&lt;/code&gt; does not list &lt;code&gt;en_US.UTF-8&lt;/code&gt;, do not install extra locales on a production runner just to imitate a laptop. Pin the script instead.&lt;/p&gt;

&lt;p&gt;Watch the two checksums. If they differ, you have the bug in miniature. If they match, keep the uppercase file. C locale puts &lt;code&gt;B.json&lt;/code&gt; before &lt;code&gt;a.json&lt;/code&gt;. Many UTF-8 locales do not. That single swap is enough to poison a concatenated OpenAPI file, a SQL migration chain, or a changelog blob.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make order a reviewed artifact
&lt;/h2&gt;

&lt;p&gt;Do not set a locale in CI and hope the next generated script obeys it. Put the contract in the file that ships.&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: pack.sh — manifest first, POSIX collation as belt-and-suspenders&lt;/span&gt;
&lt;span class="c"&gt;#!/usr/bin/env bash&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;export &lt;/span&gt;&lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LANG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C

&lt;span class="nv"&gt;root&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;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="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="s2"&gt;/fragments.manifest"&lt;/span&gt;
&lt;span class="nv"&gt;out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="s2"&gt;/bundle.json"&lt;/span&gt;

&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; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$list&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;"missing &lt;/span&gt;&lt;span class="nv"&gt;$list&lt;/span&gt;&lt;span class="s2"&gt;"&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;2
&lt;span class="k"&gt;fi&lt;/span&gt;

: &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; rel &lt;span class="o"&gt;||&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="nv"&gt;$rel&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;do&lt;/span&gt;
  &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$rel&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$rel&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="se"&gt;\#&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;continue
  &lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$rel&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="o"&gt;!&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;$path&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;"missing fragment &lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="s2"&gt;"&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;3
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$list&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep &lt;code&gt;fragments.manifest&lt;/code&gt; in git. One relative path per line. Comments allowed. An agent may still draft that list. You still review the order as a product decision, because schema fragments and migrations are ordered documents.&lt;/p&gt;

&lt;p&gt;If a manifest is truly impractical, sort with a stable key instead of trusting the glob:&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: fallback only for generated snapshots, not schema history&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C
&lt;span class="nb"&gt;mapfile&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; files &amp;lt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="s2"&gt;/fragments"&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.json'&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f | &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C &lt;span class="nb"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;find&lt;/code&gt; plus &lt;code&gt;sort&lt;/code&gt; beats a raw glob. It still loses to a reviewed list. Use the fallback for snapshot dumps. Do not use it for anything whose sequence is load-bearing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gate the tree, not the transcript
&lt;/h2&gt;

&lt;p&gt;Add a check that never needs the original agent host. It reads your scripts and your paths.&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;proposal: collation_gate.py — template, not a shipped metric.&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;os&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;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;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;UNQUOTED_GLOB&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;(?&amp;lt;![\"&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\w])([A-Za-z0-9_./-]*\*[A-Za-z0-9_./-]*)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;SKIP_DIRS&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;.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;.venv&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;node_modules&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;__pycache__&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;.mypy_cache&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;tracked_files&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;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;list&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;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="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;-C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ls-files&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;-z&lt;/span&gt;&lt;span class="sh"&gt;"&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="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="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;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;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&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;if&lt;/span&gt; &lt;span class="ow"&gt;not&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;continue&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;append&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="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="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bash_glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dir_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;lc_all&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;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;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LC_ALL&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;lc_all&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LANG&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;lc_all&lt;/span&gt;
    &lt;span class="n"&gt;quoted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dir_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_posix&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&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="sh"&gt;"'&lt;/span&gt;&lt;span class="se"&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;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="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bash&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;-lc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;printf &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%s&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;n&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;quoted&lt;/span&gt;&lt;span class="si"&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;check&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;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;env&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="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;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;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;line&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;collation_problems&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;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;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;hits&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="n"&gt;dirs&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;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&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="nf"&gt;tracked_files&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="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="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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;LC_ALL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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;LANG&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dirs&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;SKIP_DIRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;intersection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;relative_to&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;parts&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;c_order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bash_glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;here&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bash_glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current&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;c_order&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c_order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;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;rel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;relative_to&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;as_posix&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="n"&gt;hits&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;collation drift in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/ under LC_ALL=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="si"&gt;!r}&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;hits&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;glob_problems&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;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;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;hits&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;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;tracked_files&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="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="n"&gt;suffix&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.sh&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;.bash&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;.zsh&lt;/span&gt;&lt;span class="sh"&gt;"&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;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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&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;splitlines&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;stripped&lt;/span&gt; &lt;span class="o"&gt;=&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="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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LC_ALL=C&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;text&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sort&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;stripped&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;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UNQUOTED_GLOB&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;stripped&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;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;rel&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;relative_to&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;as_posix&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;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;unquoted glob &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;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="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="n"&gt;hits&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;case_collisions&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;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;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;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;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="n"&gt;hits&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;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;tracked_files&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;rel&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;relative_to&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;as_posix&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="n"&gt;rel&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;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;seen&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;rel&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;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;case collision &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;seen&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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; vs &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rel&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;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;seen&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;rel&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hits&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;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="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="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;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&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;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;problems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;collation_problems&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="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;glob_problems&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="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;case_collisions&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;problems&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;collation_gate: 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="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;collation_gate: fail&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;problems&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;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;p&gt;Run it on a clean checkout. Pass the repo root if you are not already there.&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: local invocation&lt;/span&gt;
python3 collation_gate.py &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire the same command into GitLab CI so the gate does not depend on a laptop locale. Keep the image boring.&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: .gitlab-ci.yml fragment — unexecuted template&lt;/span&gt;
&lt;span class="na"&gt;collation_gate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python:3.12-bookworm&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;locale&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python3 collation_gate.py .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The job should fail closed. A missing &lt;code&gt;fragments.manifest&lt;/code&gt; is a product defect, not a warning you scroll past.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide with a table, then stop debating hosts
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you observed&lt;/th&gt;
&lt;th&gt;Safe to merge?&lt;/th&gt;
&lt;th&gt;What to require&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unquoted glob, no &lt;code&gt;LC_ALL=C&lt;/code&gt;, checksum test only&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Manifest or POSIX sort in the script&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Glob plus &lt;code&gt;export LC_ALL=C&lt;/code&gt; and a CI locale dump&lt;/td&gt;
&lt;td&gt;Maybe&lt;/td&gt;
&lt;td&gt;Human review of file order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reviewed &lt;code&gt;fragments.manifest&lt;/code&gt; committed next to &lt;code&gt;pack.sh&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes, as order&lt;/td&gt;
&lt;td&gt;Still run the checksum in CI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two tracked paths differ only by case&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Rename on a case-sensitive clone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Laptop green, CI red, locales never printed&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Print &lt;code&gt;locale&lt;/code&gt; on both sides first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent said "all files included" and showed stdout&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Compare git paths, not chat&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Maybe" still needs a reviewer. Tables do not merge requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  A spare Linux shell helps. Its locale is not the contract
&lt;/h2&gt;

&lt;p&gt;If you work on macOS and the runner is Linux, you need one case-sensitive shell to see the real tree. That is the whole reason to use a remote box for this check.&lt;/p&gt;

&lt;p&gt;MonkeyCode offers free model access and a free server option, which is enough to draft &lt;code&gt;pack.sh&lt;/code&gt; and then rerun the concat under Linux. Disclosure: This article was prepared as part of MonkeyCode's product outreach. Use that server to print &lt;code&gt;locale&lt;/code&gt;, run &lt;code&gt;collation_gate.py&lt;/code&gt;, and catch &lt;code&gt;Auth.json&lt;/code&gt; versus &lt;code&gt;auth.json&lt;/code&gt;. Do not use it as the definition of production order.&lt;/p&gt;

&lt;p&gt;Copy the locale dump into the merge request. Leave the generated script behind only after the manifest exists. The free box is a rehearsal stage. The committed list is the show.&lt;/p&gt;

&lt;h2&gt;
  
  
  Numbered path from red CI to a mergeable script
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Print &lt;code&gt;locale&lt;/code&gt; in CI and on the machine that wrote &lt;code&gt;pack.sh&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Recreate the two-locale concat in a throwaway directory.&lt;/li&gt;
&lt;li&gt;Replace the glob with &lt;code&gt;fragments.manifest&lt;/code&gt; or with &lt;code&gt;find | LC_ALL=C sort&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Commit the manifest next to the script.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;python3 collation_gate.py .&lt;/code&gt; on a case-sensitive clone.&lt;/li&gt;
&lt;li&gt;Fail the pipeline if the gate exits 2.&lt;/li&gt;
&lt;li&gt;Review the order as a behavior change, then merge.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Skip step 7 and you only moved the bug from the glob into a file nobody read.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this gate does not prove
&lt;/h2&gt;

&lt;p&gt;A stable sort is not a schema review. A manifest can still concatenate the wrong documents in a confident order. Checksums do not tell you the JSON is valid.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LC_ALL=C&lt;/code&gt; is also the wrong hammer for user-facing sort. If the product must sort names for German or Swedish readers, do that in application code with an explicit locale. Do not inherit it from a build host.&lt;/p&gt;

&lt;p&gt;Skip this workflow when the artifact is a locale-aware UI list. Skip it when you cannot run a POSIX shell. Skip it for regulated trees you must not send to a third-party host. Solo spikes can live with a glob for an afternoon. Ordered migrations cannot.&lt;/p&gt;

&lt;p&gt;Clocks, containers, and &lt;code&gt;uname&lt;/code&gt; will not save you here. The file list will.&lt;/p&gt;

&lt;h2&gt;
  
  
  What belongs in the merge request
&lt;/h2&gt;

&lt;p&gt;Paste the locale dump. Paste the manifest diff. Paste the gate output.&lt;/p&gt;

&lt;p&gt;If the answer is a screenshot of an agent saying the bundle looks complete, reject the request. Completeness is not order. Order is data. Put that data in git before anyone concatenates another file.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>testing</category>
      <category>git</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Agent Wrote Tests. Did Anything Actually Run?</title>
      <dc:creator>Jordan Huang</dc:creator>
      <pubDate>Sat, 19 Sep 2026 10:23:22 +0000</pubDate>
      <link>https://dev.to/gitlab_3188/the-agent-wrote-tests-did-anything-actually-run-4lch</link>
      <guid>https://dev.to/gitlab_3188/the-agent-wrote-tests-did-anything-actually-run-4lch</guid>
      <description>&lt;p&gt;Did your coding agent just add tests today?&lt;br&gt;
Then I have one unkind follow-up question.&lt;/p&gt;

&lt;p&gt;What process executed those newly added test files?&lt;br&gt;
Which exit code did that process return?&lt;/p&gt;

&lt;p&gt;I keep hearing four claims in review threads.&lt;br&gt;
They sound like diligence, but they are theater.&lt;/p&gt;

&lt;p&gt;This FAQ busts those four repeated claims.&lt;br&gt;
Then it gives you a claimed-versus-actual proof pack.&lt;/p&gt;
&lt;h2&gt;
  
  
  The background nobody wants to name
&lt;/h2&gt;

&lt;p&gt;Chat UIs still reward a complete-sounding story.&lt;br&gt;
A model can narrate a green suite in one breath.&lt;/p&gt;

&lt;p&gt;You paste that story into a pull request.&lt;br&gt;
Busy reviewers treat the story as a result.&lt;/p&gt;

&lt;p&gt;Have you shipped that story this month?&lt;/p&gt;

&lt;p&gt;Trend pieces keep arguing about vibe coding lately.&lt;br&gt;
The real engineering failure is smaller and meaner.&lt;/p&gt;

&lt;p&gt;We confuse three objects that are not equal.&lt;br&gt;
Text, files, and runner results are different objects.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 1: A new test file means coverage
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; The agent added tests, so the change is safe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What developers repeat:&lt;/strong&gt; &lt;code&gt;test_api.py&lt;/code&gt; exists. Merge it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence that would actually count:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git tracks the test module&lt;/li&gt;
&lt;li&gt;A collector imported the module&lt;/li&gt;
&lt;li&gt;Assertions ran in a process&lt;/li&gt;
&lt;li&gt;The process exit code was zero&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Corrected mental model:&lt;/strong&gt; Authorship of a test is not execution.&lt;br&gt;
A test that never loaded is commented-out hope.&lt;/p&gt;

&lt;p&gt;Ask one blunt question at review time.&lt;br&gt;
Did an interpreter import this test module today?&lt;/p&gt;
&lt;h3&gt;
  
  
  Collect before you trust
&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;# proposed check; not a timed benchmark&lt;/span&gt;
git ls-files &lt;span class="s1"&gt;'*test*.py'&lt;/span&gt; &lt;span class="s1"&gt;'*_test.py'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/tracked_tests.txt
python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest &lt;span class="nt"&gt;--collect-only&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; /tmp/collected.txt
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"tracked=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &amp;lt; /tmp/tracked_tests.txt&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;If collect-only cannot see the file, stop.&lt;br&gt;
You are reviewing prose that happens to use &lt;code&gt;def&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Parametrize can also lie in the other direction.&lt;br&gt;
One function can explode into twenty node ids.&lt;/p&gt;

&lt;p&gt;Count nodes, not functions, when you compare claims.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 2: The recap is the suite result
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; "All fourteen tests passed."&lt;/p&gt;

&lt;p&gt;Who actually counted those fourteen tests, exactly?&lt;br&gt;
Was it the model, pytest, or a screenshot of neither?&lt;/p&gt;

&lt;p&gt;Natural language integers stay cheap to hallucinate.&lt;br&gt;
JUnit XML is boring, and that is the point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corrected mental model:&lt;/strong&gt; You should believe stored artifacts, not chat sentences.&lt;br&gt;
A sentence cannot sign a test report.&lt;/p&gt;
&lt;h3&gt;
  
  
  Decision table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chat claim&lt;/th&gt;
&lt;th&gt;Evidence?&lt;/th&gt;
&lt;th&gt;Why it fails or holds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"All tests passed"&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No runner identity, no exit code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pasted pytest summary&lt;/td&gt;
&lt;td&gt;Weak&lt;/td&gt;
&lt;td&gt;Transcripts are easy to fabricate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;junit.xml&lt;/code&gt; on disk&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;The runner wrote it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI log URL&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;A third system stored it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pass count without skips&lt;/td&gt;
&lt;td&gt;Careful&lt;/td&gt;
&lt;td&gt;Skips can inflate a green story&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pass count without errors&lt;/td&gt;
&lt;td&gt;Careful&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;errors&lt;/code&gt; are not &lt;code&gt;failures&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pytest treats import crashes as errors, not failures.&lt;br&gt;
Agents often fold errors into failed or hide them.&lt;/p&gt;

&lt;p&gt;Did the recap mention skipped tests at all?&lt;br&gt;
Did it mention xfail or collection errors?&lt;/p&gt;

&lt;p&gt;If those words never appeared, distrust the integer.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 3: Self-check is a second run
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; The agent re-read the tests and confirmed them.&lt;/p&gt;

&lt;p&gt;That is one narrator grading its own homework.&lt;br&gt;
Would you let the compiler mark its own exam?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corrected mental model:&lt;/strong&gt; The real oracle always lives outside the chat.&lt;br&gt;
A host process is an oracle, and a recap is not.&lt;/p&gt;

&lt;p&gt;You need a split, not a pep talk.&lt;br&gt;
Generate fixtures in one place, then execute in another.&lt;/p&gt;

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

&lt;p&gt;A practical split can look like this.&lt;br&gt;
Draft the harness with MonkeyCode's free model access.&lt;br&gt;
Then execute that harness on the free server option.&lt;/p&gt;

&lt;p&gt;The value is the split, not the logo.&lt;br&gt;
Any model plus any scratch VM can do this.&lt;/p&gt;

&lt;p&gt;Keep generation away from the oracle on purpose.&lt;br&gt;
That split is the whole method here.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 4: Skipping the runner saves time
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; The diff is tiny, so tests are overhead.&lt;/p&gt;

&lt;p&gt;Tiny diffs still rename symbols under you.&lt;br&gt;
Tiny diffs still invert a boolean in a guard.&lt;/p&gt;

&lt;p&gt;The hour you save becomes incident time later.&lt;br&gt;
That trade looks smart until production pages you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corrected mental model:&lt;/strong&gt; The test runner remains the cheapest review you have.&lt;br&gt;
A short pytest run beats a long outage thread.&lt;/p&gt;

&lt;p&gt;Also, coding agents retry without telling you.&lt;br&gt;
Retries without an oracle duplicate side effects you never measured.&lt;/p&gt;

&lt;p&gt;You do not need a lab for this lesson.&lt;br&gt;
You need an exit code on disk.&lt;/p&gt;
&lt;h2&gt;
  
  
  Artifact: claimed versus actual
&lt;/h2&gt;

&lt;p&gt;You should stop arguing with the chat transcript.&lt;br&gt;
Serialize both stories, then compare them on disk.&lt;/p&gt;

&lt;p&gt;This script is only a proposed oracle.&lt;br&gt;
It is not a vendor benchmark, and it is not timed.&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;claimed_vs_actual.py — proposed oracle, not production proof.&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;import&lt;/span&gt; &lt;span class="n"&gt;xml.etree.ElementTree&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ET&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;CLAIM&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;claimed.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;JUNIT&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;junit.xml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;PROOF&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;proof.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;def&lt;/span&gt; &lt;span class="nf"&gt;load_claim&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;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&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="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;CLAIM&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;passed&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;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;passed&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="nf"&gt;int&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;skipped&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;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;skipped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skipped&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_suite&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;cmd&lt;/span&gt; &lt;span class="o"&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="n"&gt;executable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-m&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;pytest&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;-q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--junitxml=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;JUNIT&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="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&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;cmd&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_junit&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;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&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="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JUNIT&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getroot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;suites&lt;/span&gt; &lt;span class="o"&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;root&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;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;testsuites&lt;/span&gt;&lt;span class="sh"&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;root&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;skipped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;suite&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;suites&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tests&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;suite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attrib&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;tests&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;failures&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;suite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attrib&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;failures&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;errors&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;suite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attrib&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;errors&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;skipped_n&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;suite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attrib&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;skipped&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;failed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;
        &lt;span class="n"&gt;skipped&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;skipped_n&lt;/span&gt;
        &lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;skipped_n&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skipped&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;CLAIM&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 claimed.json; copy the agent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s counts first&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;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="n"&gt;exit_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_suite&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;JUNIT&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;pytest wrote no junit.xml; collection probably died&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;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;actual_p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual_f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual_s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_junit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;claim_p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;claim_f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;claim_s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_claim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;report&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;claimed_passed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;claim_p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claimed_failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;claim_f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claimed_skipped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;claim_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;actual_passed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;actual_p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;actual_failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;actual_f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;actual_skipped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;actual_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pytest_exit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exit_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;counts_match&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;claim_p&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;actual_p&lt;/span&gt;
            &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;claim_f&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;actual_f&lt;/span&gt;
            &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;claim_s&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;actual_s&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;runner_green&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exit_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;PROOF&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;report&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="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;report&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="n"&gt;ok&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;counts_match&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;runner_green&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="k"&gt;else&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;__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;h3&gt;
  
  
  Seed the claim from chat
&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;"passed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"failed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"skipped"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agent-recap"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"copy numbers from chat; do not trust them"&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;
  
  
  Run the pack
&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;# proposed workflow on any scratch host&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;.&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install &lt;/span&gt;pytest
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'{"passed":14,"failed":0,"skipped":0}'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; claimed.json
python claimed_vs_actual.py
&lt;span class="nb"&gt;cat &lt;/span&gt;proof.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;counts_match&lt;/code&gt; is false, the chat drifted.&lt;br&gt;
If the runner never wrote XML, you had no suite.&lt;/p&gt;

&lt;p&gt;Commit &lt;code&gt;junit.xml&lt;/code&gt; and &lt;code&gt;proof.json&lt;/code&gt; with the PR.&lt;br&gt;
Now reviewers can argue with files, not vibes.&lt;/p&gt;

&lt;h2&gt;
  
  
  One-hour checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Freeze the agent's pass, fail, and skip counts.&lt;/li&gt;
&lt;li&gt;Write those integers into &lt;code&gt;claimed.json&lt;/code&gt; before running.&lt;/li&gt;
&lt;li&gt;Collect tests with &lt;code&gt;--collect-only&lt;/code&gt; and read node ids.&lt;/li&gt;
&lt;li&gt;Execute the proof pack on a real interpreter.&lt;/li&gt;
&lt;li&gt;Attach &lt;code&gt;proof.json&lt;/code&gt; plus the JUnit file to the PR.&lt;/li&gt;
&lt;li&gt;Reject the change when &lt;code&gt;runner_green&lt;/code&gt; is false.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No dashboard is required for this gate.&lt;br&gt;
No proprietary runner is required for this either.&lt;/p&gt;

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

&lt;p&gt;Matching counts still do not mean correct assertions.&lt;br&gt;
Wrong tests that run are still wrong tests.&lt;/p&gt;

&lt;p&gt;This pack does not measure flake rates.&lt;br&gt;
It also does not measure a mutation score.&lt;br&gt;
It does not measure runtime performance at all.&lt;br&gt;
It does not measure any real security properties.&lt;/p&gt;

&lt;p&gt;Free model access is only a convenience.&lt;br&gt;
A free server option is only a convenience.&lt;br&gt;
I will not invent token quotas or hardware claims.&lt;/p&gt;

&lt;p&gt;Treat both as optional tools for the split.&lt;br&gt;
Do not treat them as a contract or an SLO.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Skip this if CI already stores JUnit on every merge.&lt;/li&gt;
&lt;li&gt;Skip this when the code cannot leave a locked network.&lt;/li&gt;
&lt;li&gt;Skip this if secrets must never reach a scratch host.&lt;/li&gt;
&lt;li&gt;Skip this when no human still owns the contract.&lt;/li&gt;
&lt;li&gt;Skip this if you want a recap to replace design work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If CI already gates merges, do not add theater.&lt;br&gt;
Wire the pack into CI, or skip the extra ritual.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep this mental model
&lt;/h2&gt;

&lt;p&gt;Three objects exist after every agent coding session.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Text&lt;/strong&gt; the model wrote in a transcript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Files&lt;/strong&gt; git actually recorded&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Results&lt;/strong&gt; a runner produced on a host&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Only object three can turn a suite green.&lt;br&gt;
Object one is a story, and object two is an input.&lt;/p&gt;

&lt;p&gt;So, did the agent write tests this time?&lt;br&gt;
Good, now make a real process run them.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>python</category>
      <category>devops</category>
    </item>
    <item>
      <title>A Green Install Is Not a Pin: Five Dependency Myths</title>
      <dc:creator>Jordan Huang</dc:creator>
      <pubDate>Sat, 19 Sep 2026 06:18:50 +0000</pubDate>
      <link>https://dev.to/gitlab_3188/a-green-install-is-not-a-pin-five-dependency-myths-hf1</link>
      <guid>https://dev.to/gitlab_3188/a-green-install-is-not-a-pin-five-dependency-myths-hf1</guid>
      <description>&lt;p&gt;Did your coding agent just fix the install? I keep seeing that victory line in traces. The command then printed a clean exit code. Then someone copies &lt;code&gt;node_modules&lt;/code&gt; like a relic.&lt;/p&gt;

&lt;p&gt;That souvenir is not a real dependency pin. It is leftover dust from a scratch disk. Want the rude version? The graph was never frozen.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this FAQ is actually about
&lt;/h2&gt;

&lt;p&gt;This is not a packaging sermon for librarians. This is about agent loops on scratch hosts. The model edits &lt;code&gt;package.json&lt;/code&gt; with a confident range. The host runs some flavor of install. Chat says the work is done.&lt;/p&gt;

&lt;p&gt;Did the graph freeze? Did CI receive the same bytes? Usually no. The chat just narrated a feeling.&lt;/p&gt;

&lt;p&gt;I wrote this as a myth list you can run. Each myth has a check. Each check has a command you can paste. Steal the script at the end. Then argue with hashes, not vibes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 1: A package.json edit is a pin
&lt;/h2&gt;

&lt;p&gt;The agent bumped a version range. The range looks adult and responsible. You feel oddly safe.&lt;/p&gt;

&lt;p&gt;A range is a wish. A lockfile is a map. Which file would you restore after a fire? If the answer is &lt;code&gt;package.json&lt;/code&gt; alone, you will drift.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evidence you can collect in two minutes
&lt;/h3&gt;

&lt;p&gt;Run these in the repo the agent touched:&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;--stat&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; package.json package-lock.json yarn.lock pnpm-lock.yaml
git status &lt;span class="nt"&gt;--porcelain&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; package.json &lt;span class="s1"&gt;'*lock*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at the porcelain lines. Did the lockfile move with the manifest? If the lockfile stayed silent, the pin is theater.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manifest-only diff: the agent wrote a wish.&lt;/li&gt;
&lt;li&gt;Lockfile-only diff: someone regenerated in the dark.&lt;/li&gt;
&lt;li&gt;Both files in one commit: you might have a pin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Corrected model: treat the lockfile as the artifact. Treat the manifest as commentary on intent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 2: Install exit code 0 means the graph is stable
&lt;/h2&gt;

&lt;p&gt;Green text feels like proof. Agents love green text. Humans do too. Why would we not?&lt;/p&gt;

&lt;p&gt;Exit code 0 means the installer did not die. It does not mean the tree is replayable tomorrow. Optional deps fail open. Peer deps warn and continue. Platform binaries swap without a speech.&lt;/p&gt;

&lt;p&gt;Lifecycle scripts can mutate files after the resolver stops. Did you capture that? Or only the last happy line?&lt;/p&gt;

&lt;h3&gt;
  
  
  A tiny probe
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/install-dry.json
&lt;span class="c"&gt;# frozen path, when a lockfile already exists&lt;/span&gt;
npm ci &lt;span class="nt"&gt;--ignore-scripts&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the warnings. Count peer issues. Did a native addon pick a platform? If you only stored agent stdout, you stored a mood. You did not store a graph.&lt;/p&gt;

&lt;p&gt;Corrected model: success is a frozen installer command. Add a hashed lockfile. Keep both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 3: The scratch host's node_modules will match CI
&lt;/h2&gt;

&lt;p&gt;This one bites teams on free servers. The sandbox is some Linux userland. Your laptop is not that userland. CI might use another Node line.&lt;/p&gt;

&lt;p&gt;The agent compiled a binary. The binary linked a glibc it found. Your Mac never will. So why copy &lt;code&gt;node_modules&lt;/code&gt; off a scratch disk? You are copying an accident with extra steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fingerprint before you trust the tree
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt;
node &lt;span class="nt"&gt;-v&lt;/span&gt;
corepack &lt;span class="nt"&gt;--version&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;git rev-parse HEAD
&lt;span class="nb"&gt;sha256sum &lt;/span&gt;package-lock.json 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; shasum &lt;span class="nt"&gt;-a&lt;/span&gt; 256 package-lock.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put those five lines next to the test output. If CI disagrees on any line, the tree is local color. It is not a release.&lt;/p&gt;

&lt;p&gt;I replay some of these checks on MonkeyCode. Disclosure: This article was prepared as part of MonkeyCode's product outreach. It offers free model access and a free server option. That pair is a scratch lane for me. It is not a substitute for CI. I still hash the lockfile. I still refuse to ship the sandbox tree.&lt;/p&gt;

&lt;p&gt;Corrected model: the host is a lab bench. CI remains the court of bytes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 4: Tests passing means you can skip the lockfile
&lt;/h2&gt;

&lt;p&gt;The agent ran the unit suite. All dots went green. Someone deleted the lockfile from the PR. Review got quieter. Supply chain got louder.&lt;/p&gt;

&lt;p&gt;I get the temptation. Lockfiles are noisy diffs. Reviewers complain about churn. Tests sample behavior. They do not sample the entire graph. A transitive bump can stay green. It can still change who you trust.&lt;/p&gt;

&lt;h3&gt;
  
  
  The check I actually want in review
&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;# Fail the job if the lockfile moves after a frozen install&lt;/span&gt;
npm ci
git diff &lt;span class="nt"&gt;--exit-code&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; package-lock.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that exits 1, the agent did not pin. The PR is incomplete. Green tests with a dirty lock are a failed job. Say that in the template.&lt;/p&gt;

&lt;p&gt;Corrected model: a suite is evidence of behavior. A lock hash is evidence of the graph. You need both on the same commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 5: Re-running the same prompt rebuilds the same tree
&lt;/h2&gt;

&lt;p&gt;Agents do not replay like compilers. Prompts are not makefiles. Registries move. Resolvers change defaults. Models improvise a second "fix".&lt;/p&gt;

&lt;p&gt;You hit retry. You got another story. The second tree is not the first tree. Did you keep the first lockfile hash? If not, you cannot detect the drift. You can only reread the chat.&lt;/p&gt;

&lt;h3&gt;
  
  
  Capture once, compare forever
&lt;/h3&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; .replay
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"head=&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;"node=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;node &lt;span class="nt"&gt;-v&lt;/span&gt;&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;"lock=&lt;/span&gt;&lt;span class="si"&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;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="o"&gt;&amp;gt;&lt;/span&gt; .replay/pin.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next run, diff the file. If &lt;code&gt;lock=&lt;/code&gt; changed, you did not reproduce. You rolled the dice and named it science.&lt;/p&gt;

&lt;p&gt;Corrected model: reproduction is hash equality. Chat similarity is noise with punctuation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Artifact: a pin-proof script and a decision table
&lt;/h2&gt;

&lt;p&gt;Do not copy the sandbox. Prove the pin. Here is a script I keep as &lt;code&gt;scripts/pin_proof.sh&lt;/code&gt;. Treat it as a labeled workflow sample. Adapt the paths. I am not claiming production metrics.&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;# pin_proof.sh — record whether an agent install is actually pinned&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;out&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="p"&gt;.replay/pin_proof.json&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&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;$out&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="nv"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;cand &lt;span class="k"&gt;in &lt;/span&gt;package-lock.json pnpm-lock.yaml yarn.lock&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  if&lt;/span&gt; &lt;span class="o"&gt;[[&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;$cand&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="nv"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cand&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;break
  &lt;/span&gt;&lt;span class="k"&gt;fi
done

if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$lock&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;"no lockfile found"&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;2
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;hash&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;sha256sum&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$lock&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;dirty&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;git status &lt;span class="nt"&gt;--porcelain&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$lock&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; package.json &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="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;head&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;git rev-parse HEAD 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;unborn&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;node_v&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;node &lt;span class="nt"&gt;-v&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;missing&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;os&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;uname&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;install_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"skip"&lt;/span&gt;
&lt;span class="nv"&gt;lock_after&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"not_npm"&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="nv"&gt;$lock&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"package-lock.json"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; npm &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;npm ci &lt;span class="nt"&gt;--ignore-scripts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;install_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"npm_ci_ok"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nv"&gt;install_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"npm_ci_failed"&lt;/span&gt;
  &lt;span class="k"&gt;fi
  if &lt;/span&gt;git diff &lt;span class="nt"&gt;--exit-code&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; package-lock.json&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;lock_after&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"unchanged"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nv"&gt;lock_after&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mutated"&lt;/span&gt;
  &lt;span class="k"&gt;fi
fi

&lt;/span&gt;python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;
import json, os
doc = {
  "git_head": "&lt;/span&gt;&lt;span class="nv"&gt;$head&lt;/span&gt;&lt;span class="sh"&gt;",
  "os": "&lt;/span&gt;&lt;span class="nv"&gt;$os&lt;/span&gt;&lt;span class="sh"&gt;",
  "node": "&lt;/span&gt;&lt;span class="nv"&gt;$node_v&lt;/span&gt;&lt;span class="sh"&gt;",
  "lockfile": "&lt;/span&gt;&lt;span class="nv"&gt;$lock&lt;/span&gt;&lt;span class="sh"&gt;",
  "lock_sha256": "&lt;/span&gt;&lt;span class="nv"&gt;$hash&lt;/span&gt;&lt;span class="sh"&gt;",
  "lock_dirty_before": """&lt;/span&gt;&lt;span class="nv"&gt;$dirty&lt;/span&gt;&lt;span class="sh"&gt;""".strip(),
  "install": "&lt;/span&gt;&lt;span class="nv"&gt;$install_ok&lt;/span&gt;&lt;span class="sh"&gt;",
  "lock_after_ci": "&lt;/span&gt;&lt;span class="nv"&gt;$lock_after&lt;/span&gt;&lt;span class="sh"&gt;",
}
os.makedirs(os.path.dirname("&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="sh"&gt;") or ".", exist_ok=True)
with open("&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="sh"&gt;", "w") as f:
    json.dump(doc, f, indent=2)
print(json.dumps(doc, indent=2))
&lt;/span&gt;&lt;span class="no"&gt;PY

&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="nv"&gt;$install_ok&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"npm_ci_failed"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$lock_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;"mutated"&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;"pin proof failed"&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;Run it after the agent stops 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="nb"&gt;chmod&lt;/span&gt; +x scripts/pin_proof.sh
./scripts/pin_proof.sh .replay/pin_proof.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit &lt;code&gt;.replay/pin_proof.json&lt;/code&gt; only if your team wants the fingerprint. Do not commit &lt;code&gt;node_modules&lt;/code&gt;. Ever. That directory is a host opinion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Decision table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Observation&lt;/th&gt;
&lt;th&gt;Trust the tree?&lt;/th&gt;
&lt;th&gt;Next action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No lockfile&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Generate one with a frozen installer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lock dirty after &lt;code&gt;npm ci&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Send the agent back at the lock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;npm ci&lt;/code&gt; fails, &lt;code&gt;npm install&lt;/code&gt; works&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;The graph is not pinned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OS or Node mismatch versus CI&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Replay on the CI image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lock hash matches CI cache&lt;/td&gt;
&lt;td&gt;Yes, as a pin&lt;/td&gt;
&lt;td&gt;Still run the real pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests green, lock missing&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Tests are not a resolver&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Print the table in review. Argue with the row. Do not argue with the chat transcript.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical replay workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Freeze the git &lt;code&gt;HEAD&lt;/code&gt; the agent actually saw.&lt;/li&gt;
&lt;li&gt;Require a lockfile in that same commit.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;scripts/pin_proof.sh&lt;/code&gt; on the scratch host.&lt;/li&gt;
&lt;li&gt;Run the same script on CI or a matching image.&lt;/li&gt;
&lt;li&gt;Merge only when lock hashes match and &lt;code&gt;npm ci&lt;/code&gt; stays quiet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Need a scratch lane for step 3? A free server is enough for the fingerprint. A free model can propose the lock update. Neither one gets merge rights. Tools propose. Proof files dispose.&lt;/p&gt;

&lt;p&gt;That is the whole trick. Keep the rights off the chat.&lt;/p&gt;

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

&lt;p&gt;This script does not audit the registry. It does not catch a compromised tarball with the same version. It does not replace &lt;code&gt;npm audit&lt;/code&gt;. It does not emit a real SBOM.&lt;/p&gt;

&lt;p&gt;It assumes a Node repo. Cargo, Go, and pip need the same idea. Swap the frozen command. Keep the hash. Keep the OS line.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm ci --ignore-scripts&lt;/code&gt; skips lifecycle traps. It also skips needed native builds. If your package requires scripts, run a second pass in CI. Do not hide that pass on a laptop and call it policy.&lt;/p&gt;

&lt;p&gt;I am not publishing timings. Timings rot by next week. Hashes do not.&lt;/p&gt;

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

&lt;p&gt;Do not use this as your only release gate. Do not use this if you cannot run &lt;code&gt;npm ci&lt;/code&gt;. Do not use this to skip reviewers because the JSON looks official.&lt;/p&gt;

&lt;p&gt;If you ship mobile binaries, this Node fingerprint is incomplete. If you vendor native addons, hash those blobs too. If you need a legal provenance chain, use signed attestations. This FAQ is a lab checklist, not a courtroom.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I want you to remember
&lt;/h2&gt;

&lt;p&gt;The agent did not pin that range. The installer did not freeze the universe. The scratch disk is not your product. Ask one rude question in review. Where is the lock hash?&lt;/p&gt;

&lt;p&gt;If the hash is missing, the work is missing. If the hash moved, you did not reproduce. You just got another story with a green exit code.&lt;/p&gt;

&lt;p&gt;Run the script on the next agent PR. Paste the JSON in the review. That is the only ask.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>testing</category>
      <category>git</category>
    </item>
    <item>
      <title>FAQ: Five Myths About Tests the Agent Wrote for Itself</title>
      <dc:creator>Jordan Huang</dc:creator>
      <pubDate>Sat, 19 Sep 2026 03:12:00 +0000</pubDate>
      <link>https://dev.to/gitlab_3188/faq-five-myths-about-tests-the-agent-wrote-for-itself-5h8o</link>
      <guid>https://dev.to/gitlab_3188/faq-five-myths-about-tests-the-agent-wrote-for-itself-5h8o</guid>
      <description>&lt;p&gt;Who actually graded the agent's homework on that run?&lt;br&gt;
I keep seeing the same claim in review threads.&lt;br&gt;
The agent wrote tests, they passed, so we ship.&lt;/p&gt;

&lt;p&gt;That claim is circular because the grader sat nearby.&lt;br&gt;
The same session wrote the answers and the key.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why this FAQ exists
&lt;/h2&gt;

&lt;p&gt;Green test output is not independent evidence of correctness.&lt;br&gt;
The same loop wrote the code and the checks.&lt;br&gt;
Ask a harder question before you merge.&lt;/p&gt;

&lt;p&gt;Did the tests constrain the implementation at all?&lt;br&gt;
Or did they photograph whatever the model just invented?&lt;/p&gt;

&lt;p&gt;I use this FAQ when the test tree appears in one burst.&lt;br&gt;
New files in &lt;code&gt;src/&lt;/code&gt; and &lt;code&gt;tests/&lt;/code&gt; often share one commit.&lt;br&gt;
You still need a mental model, not a screenshot.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 1: Agent-written tests are a second opinion
&lt;/h2&gt;

&lt;p&gt;Standup version sounds responsible and even grown-up.&lt;br&gt;
The model implemented the feature, then added tests as review.&lt;/p&gt;

&lt;p&gt;What I actually see looks much less independent.&lt;br&gt;
The tests import the same brand-new module.&lt;br&gt;
They assert the happy path the prompt already described.&lt;/p&gt;

&lt;p&gt;Demote that suite before you treat it as review.&lt;br&gt;
Treat those tests as documentation of intended shape.&lt;br&gt;
They are not a rival reviewer with rival incentives.&lt;/p&gt;

&lt;p&gt;Same session, same context, same hunger for green.&lt;br&gt;
Would you let a candidate grade their own take-home?&lt;br&gt;
I still want the tests, but I demote them hard.&lt;/p&gt;

&lt;p&gt;They are clues about intent, not a court verdict.&lt;/p&gt;
&lt;h3&gt;
  
  
  Commands I run on the commit
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;--diff-filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;A &lt;span class="nt"&gt;--summary&lt;/span&gt; &lt;span class="nt"&gt;--since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'2 hours ago'&lt;/span&gt;
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt; HEAD~1
git log &lt;span class="nt"&gt;--follow&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; tests/ | &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Look at which files arrived together this hour.&lt;br&gt;
Implementation and tests in one commit is a smell.&lt;br&gt;
That pattern is a smell, not an automatic ban.&lt;/p&gt;

&lt;p&gt;If src and tests move in lockstep, pause there.&lt;br&gt;
Ask which file could fail without the other changing.&lt;br&gt;
If the answer is none, you have a mirror suite.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 2: A high coverage number pins behavior
&lt;/h2&gt;

&lt;p&gt;People quote coverage like it were a lock on meaning.&lt;br&gt;
Coverage is high, so the agent covered the behavior.&lt;/p&gt;

&lt;p&gt;Coverage counts executed lines, not meaningful decision branches.&lt;br&gt;
A tautology still paints a source line green.&lt;br&gt;
Asserting a function equals itself pins nothing real.&lt;/p&gt;

&lt;p&gt;Coverage is a flashlight, and it is not a lock.&lt;br&gt;
Ask which inputs would change the assertion today.&lt;br&gt;
If you cannot name two, the test is a tour.&lt;/p&gt;
&lt;h3&gt;
  
  
  Proposal: scan for assertions that cannot fail
&lt;/h3&gt;

&lt;p&gt;This snippet is a proposal, not a scored benchmark.&lt;br&gt;
Adapt the root path before you run anything.&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: tautology_scan.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Compare&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;walk&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;sys&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_tautology&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Assert&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="n"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&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;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Compare&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;comparators&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;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rights&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;scan&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;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;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse&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;for&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;is_tautology&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&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;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="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="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lineno&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="n"&gt;hits&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;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="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;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;rglob&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_*.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;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="nf"&gt;scan&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;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;/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;python3 tautology_scan.py tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it against the agent's test directory only.&lt;br&gt;
Empty output is not safety, only one cheap filter.&lt;br&gt;
You still need failing cases that you wrote.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 3: Mocks mean the agent understood the boundary
&lt;/h2&gt;

&lt;p&gt;Does a MagicMock prove the adapter was actually designed?&lt;br&gt;
The agent mocked the database, so the tests are hermetic.&lt;/p&gt;

&lt;p&gt;Mocks often return the shape the implementation already assumed.&lt;br&gt;
The test never met the real adapter or the real error.&lt;br&gt;
Isolation can freeze a fantasy instead of a contract.&lt;/p&gt;

&lt;p&gt;A mock is a second copy of the same story.&lt;br&gt;
Compare mock payloads with a real recorded fixture.&lt;br&gt;
If they only exist in the test file, be suspicious.&lt;/p&gt;
&lt;h3&gt;
  
  
  Hunt the stuffed animals
&lt;/h3&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;-R&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"MagicMock&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;AsyncMock&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;mock.patch&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;monkeypatch"&lt;/span&gt; tests &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"return_value&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;side_effect"&lt;/span&gt; tests &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Search for return_value lines and read them slowly.&lt;br&gt;
Would that payload survive a schema change next month?&lt;br&gt;
If not, the mock is a stuffed animal, not a fence.&lt;/p&gt;

&lt;p&gt;Prefer one real fixture over five agreeable mocks.&lt;br&gt;
You do not need production data for that fixture.&lt;br&gt;
Store it outside the session if the loop can edit tests.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 4: Hiding tests from the prompt keeps them honest
&lt;/h2&gt;

&lt;p&gt;Prompt folklore says secrecy equals scientific control.&lt;br&gt;
I never pasted the tests, so the model cannot cheat.&lt;/p&gt;

&lt;p&gt;The model still writes both sides of the bargain.&lt;br&gt;
It does not need your old tests to invent mirrors.&lt;br&gt;
The filesystem is in the loop with the repo.&lt;/p&gt;

&lt;p&gt;If the agent can list tests, it can match names.&lt;br&gt;
Honesty is a process, not a clever prompt trick.&lt;br&gt;
Hold out a check the agent cannot edit later.&lt;/p&gt;

&lt;p&gt;I keep a tiny golden file outside the working tree.&lt;br&gt;
Or I run one human test after the loop stops.&lt;/p&gt;
&lt;h3&gt;
  
  
  Hold-out pattern
&lt;/h3&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; /tmp/holdout
&lt;span class="nb"&gt;cp &lt;/span&gt;tests/test_invariants.py /tmp/holdout/
&lt;span class="c"&gt;# run the agent against the repo only&lt;/span&gt;
pytest /tmp/holdout/test_invariants.py &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-ra&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The hold-out file must predate the agent session.&lt;br&gt;
If the agent wrote it, it is not a hold-out.&lt;br&gt;
Simple question: did you type that file yesterday?&lt;/p&gt;

&lt;p&gt;If not, it does not count as external proof.&lt;br&gt;
A copied file from this afternoon still shares authorship.&lt;br&gt;
Time travel in git does not create a second mind.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 5: Snapshot files are regression armor
&lt;/h2&gt;

&lt;p&gt;Snapshot tooling feels like a museum alarm for JSON.&lt;br&gt;
The agent added snapshots, so future diffs will save us.&lt;/p&gt;

&lt;p&gt;First snapshots can encode the first hallucination cleanly.&lt;br&gt;
A golden file of wrong JSON still fails later.&lt;br&gt;
That failure protects the bug, not the user.&lt;/p&gt;

&lt;p&gt;Snapshots freeze a story, so check the story first.&lt;br&gt;
Read the snapshot instead of watching only the matcher.&lt;br&gt;
If you cannot explain one field, delete the file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.snap'&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;'*__snapshots__*'&lt;/span&gt;
find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.snap'&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; | xargs &lt;span class="nt"&gt;-I&lt;/span&gt;&lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 40 &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask three questions out loud before you commit snapshots.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What field is required for the user contract?&lt;/li&gt;
&lt;li&gt;What field is incidental formatting or noise?&lt;/li&gt;
&lt;li&gt;What field would a wrong model still get right?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If every field is incidental, the snapshot is theater.&lt;br&gt;
Theater can still fail CI and waste a whole afternoon.&lt;br&gt;
That is not armor. That is costume jewelry.&lt;/p&gt;
&lt;h2&gt;
  
  
  Artifact: a decision table for the PR body
&lt;/h2&gt;

&lt;p&gt;I keep this table next to the pull request template.&lt;br&gt;
It is a workflow, not a benchmark with fake numbers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Observation&lt;/th&gt;
&lt;th&gt;Do not conclude&lt;/th&gt;
&lt;th&gt;Conclude instead&lt;/th&gt;
&lt;th&gt;Next command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tests arrived with the impl&lt;/td&gt;
&lt;td&gt;The suite is independent&lt;/td&gt;
&lt;td&gt;Same author, same hour&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git log --follow -- tests/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coverage looks high&lt;/td&gt;
&lt;td&gt;Behavior is pinned&lt;/td&gt;
&lt;td&gt;Lines ran once&lt;/td&gt;
&lt;td&gt;add one failing input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mocks return nested dicts&lt;/td&gt;
&lt;td&gt;The boundary is tested&lt;/td&gt;
&lt;td&gt;The fantasy was serialized&lt;/td&gt;
&lt;td&gt;&lt;code&gt;grep -R return_value tests&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt omitted tests&lt;/td&gt;
&lt;td&gt;Model cannot cheat&lt;/td&gt;
&lt;td&gt;Model can still write both&lt;/td&gt;
&lt;td&gt;hold-out file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Snapshots committed&lt;/td&gt;
&lt;td&gt;Regressions are blocked&lt;/td&gt;
&lt;td&gt;A story was frozen&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;head&lt;/code&gt; the snap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Collection count is zero&lt;/td&gt;
&lt;td&gt;Nothing broke&lt;/td&gt;
&lt;td&gt;Nothing ran&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pytest --collect-only -q&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Copy the table into the pull request body.&lt;br&gt;
Fill the next-command column with real command output.&lt;br&gt;
Output beats vibes, and that is the whole FAQ.&lt;/p&gt;
&lt;h2&gt;
  
  
  A full audit script you can copy
&lt;/h2&gt;

&lt;p&gt;Label this as a template and adapt paths before running.&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;# template: audit_agent_tests.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="nv"&gt;ROOT&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="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ROOT&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;"== new files in the last day =="&lt;/span&gt;
git log &lt;span class="nt"&gt;--since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'1 day ago'&lt;/span&gt; &lt;span class="nt"&gt;--name-only&lt;/span&gt; &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;format: | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"== test vs src lockstep =="&lt;/span&gt;
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt; HEAD~1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true

echo&lt;/span&gt; &lt;span class="s2"&gt;"== collection count =="&lt;/span&gt;
pytest &lt;span class="nt"&gt;--collect-only&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true

echo&lt;/span&gt; &lt;span class="s2"&gt;"== skip report =="&lt;/span&gt;
pytest &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-rs&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true

echo&lt;/span&gt; &lt;span class="s2"&gt;"== tautology scan =="&lt;/span&gt;
python3 tautology_scan.py tests &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true

echo&lt;/span&gt; &lt;span class="s2"&gt;"== mock payload hunt =="&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"return_value&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;side_effect"&lt;/span&gt; tests &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true

echo&lt;/span&gt; &lt;span class="s2"&gt;"== snapshot peek =="&lt;/span&gt;
find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.snap'&lt;/span&gt; &lt;span class="nt"&gt;-print&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it twice, once on a scratch host first.&lt;br&gt;
Run it again on your laptop after that.&lt;br&gt;
Diff the two transcripts before you argue about coverage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;diff &lt;span class="nt"&gt;-u&lt;/span&gt; host-audit.txt laptop-audit.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If collection counts differ, stop the merge immediately.&lt;br&gt;
If skip counts differ, stop the merge immediately.&lt;br&gt;
If only the banner differs, you can keep reading.&lt;/p&gt;

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

&lt;p&gt;I sometimes need a throwaway box for this audit.&lt;br&gt;
I do not want leftover modules from last week.&lt;br&gt;
I also do not want laptop caches lying to me.&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 use that pair when I want a clean sample host.&lt;br&gt;
The model proposes test files while the server runs the audit.&lt;/p&gt;

&lt;p&gt;Then I still replay locally because the free host is one sample.&lt;br&gt;
It is not CI, production, or a trophy screenshot.&lt;br&gt;
I will not invent quotas, hardware, or duration claims.&lt;/p&gt;

&lt;p&gt;Those details change, and this FAQ does not depend on them.&lt;br&gt;
The method is the two transcripts and the hold-out file.&lt;br&gt;
Lose the product and the checklist still stands.&lt;/p&gt;

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

&lt;p&gt;This FAQ will not catch a clever assertion.&lt;br&gt;
It will not replace a human invariant test.&lt;br&gt;
It will not prove a distributed system correct.&lt;/p&gt;

&lt;p&gt;AST tautology checks miss helper functions on purpose.&lt;br&gt;
They miss equality helpers that compare a value to itself.&lt;br&gt;
They miss generated tests in other languages entirely.&lt;/p&gt;

&lt;p&gt;The bash script assumes git and pytest are installed.&lt;br&gt;
It assumes a Python layout many repos do not share.&lt;br&gt;
Do not treat empty scanner output as a blessing.&lt;/p&gt;

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

&lt;p&gt;Do not use this checklist as a hiring score.&lt;br&gt;
Do not use this checklist as a security audit.&lt;br&gt;
Do not use this on repositories you cannot clone.&lt;/p&gt;

&lt;p&gt;Do not skip CI because a scratch host was green.&lt;br&gt;
If you ship firmware, write the hold-out yourself.&lt;br&gt;
If you handle money, write the hold-out yourself.&lt;/p&gt;

&lt;p&gt;If you have no tests at all, start there first.&lt;br&gt;
An audit of empty tests is empty theater only.&lt;br&gt;
This FAQ assumes you already have a suite to interrogate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I want you to remember
&lt;/h2&gt;

&lt;p&gt;The agent can write tests, and that is useful.&lt;br&gt;
Useful is not the same thing as independent proof.&lt;br&gt;
Same author, same hour, same incentive to go green.&lt;/p&gt;

&lt;p&gt;Hold out one check the loop cannot edit.&lt;br&gt;
Replay the suite on a second machine every time.&lt;br&gt;
Read the snapshots like they might be wrong today.&lt;/p&gt;

&lt;p&gt;They might be wrong, and that is the job.&lt;br&gt;
Would you merge a self-graded exam without a second booklet?&lt;br&gt;
I would not, and your users would not either.&lt;/p&gt;

&lt;p&gt;If you already have a scratch host, run the two-transcript diff tonight.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>devops</category>
      <category>python</category>
    </item>
    <item>
      <title>Agent stdout Is Not Your Test Plan</title>
      <dc:creator>Jordan Huang</dc:creator>
      <pubDate>Sat, 19 Sep 2026 00:03:07 +0000</pubDate>
      <link>https://dev.to/gitlab_3188/agent-stdout-is-not-your-test-plan-o4d</link>
      <guid>https://dev.to/gitlab_3188/agent-stdout-is-not-your-test-plan-o4d</guid>
      <description>&lt;p&gt;Did your agent print All tests passed again today? Did you merge because the log felt complete? That habit is how fake green sneaks in.&lt;/p&gt;

&lt;p&gt;I keep hearing four myths about agent logs. They sound like engineering. They are not a test plan.&lt;/p&gt;

&lt;p&gt;This is a myth-busting FAQ. You can delete every product name. The checklist still works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why logs became a fake CI
&lt;/h2&gt;

&lt;p&gt;Agents write confident sentences. Humans treat sentences like gates. That is a category error, right?&lt;/p&gt;

&lt;p&gt;A log line is a claim. A claim is not a process. Who launched the process you trust?&lt;/p&gt;

&lt;p&gt;I want a boring gate. I want it copyable. I want it on a scratch machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 1: A printed pass means the suite ran
&lt;/h2&gt;

&lt;p&gt;Have you grepped &lt;code&gt;agent.log&lt;/code&gt; for the word &lt;code&gt;passed&lt;/code&gt;? I have. It lies in three ways.&lt;/p&gt;

&lt;p&gt;The model can narrate a run it skipped. It can paste an old snippet. It can praise intent as if it were output.&lt;/p&gt;

&lt;p&gt;What evidence remains then? Only a process you started. Only bytes you captured. Only an exit code you stored.&lt;/p&gt;

&lt;p&gt;Corrected mental model: stdout is untrusted speech. Speech is not a run.&lt;/p&gt;

&lt;p&gt;You need four fields or you have nothing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the argv you actually invoked&lt;/li&gt;
&lt;li&gt;the working directory&lt;/li&gt;
&lt;li&gt;the numeric exit code&lt;/li&gt;
&lt;li&gt;hashes of stdout and stderr&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No fourth field? You are reading a story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 2: Exit code 0 means the change is finished
&lt;/h2&gt;

&lt;p&gt;Zero is one integer. Which contract did it sign?&lt;/p&gt;

&lt;p&gt;A formatter can return 0. A skipped pytest session can return 0. A &lt;code&gt;--dry-run&lt;/code&gt; flag can return 0. Did you name the contract first?&lt;/p&gt;

&lt;p&gt;Ask a sharper question. Did this exact argv fail? That is all zero answers.&lt;/p&gt;

&lt;p&gt;Corrected mental model: exit code is narrow. Completeness lives elsewhere. Completeness needs file lists and git votes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 3: Complete-looking logs mean a clean tree
&lt;/h2&gt;

&lt;p&gt;Logs sit in a buffer. The repo sits on disk. They drift fast.&lt;/p&gt;

&lt;p&gt;Can the agent print a diff it never wrote? Yes. Can it write files it never mentioned? Also yes. Which artifact did you review?&lt;/p&gt;

&lt;p&gt;Corrected mental model: &lt;code&gt;git status&lt;/code&gt; is a vote. &lt;code&gt;git diff --stat&lt;/code&gt; is a vote. The chat transcript is not a vote.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;HEAD&lt;/code&gt; is unknown, you are not reviewing a change. You are reviewing a vibe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 4: One green scratch run is portable
&lt;/h2&gt;

&lt;p&gt;You got a clean VM. The command passed once. Will CI share &lt;code&gt;PATH&lt;/code&gt;, lockfiles, and OS packages?&lt;/p&gt;

&lt;p&gt;A scratch host is a lab. A lab is not memory. Do not ship the lab as proof.&lt;/p&gt;

&lt;p&gt;Corrected mental model: a green run is one sample. Samples need a recipe. Recipes need pins you can paste.&lt;/p&gt;

&lt;h2&gt;
  
  
  The artifact: refuse speech, record votes
&lt;/h2&gt;

&lt;p&gt;I do not want another dashboard. I want a script that ignores English.&lt;/p&gt;

&lt;p&gt;Label this as a runnable example. I am not attaching fake timings or fake pass rates.&lt;/p&gt;

&lt;p&gt;Save &lt;code&gt;accept_run.py&lt;/code&gt; at the repo root. Make it the only gate after an agent session.&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;Acceptance harness: treat agent speech as untrusted.&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;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;os&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;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="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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;ACCEPT_ROOT&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;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;CMD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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;ACCEPT_CMD&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;python -m pytest -q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;REPORT&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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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;ACCEPT_REPORT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accept_report.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;def&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;cmd&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;cwd&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;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;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;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="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cmd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cwd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exit_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&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;returncode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stdout_sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&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;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;encode&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stderr_sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&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;proc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stdout_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;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;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;stderr_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&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;git&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="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;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="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="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="n"&gt;cwd&lt;/span&gt;&lt;span class="o"&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;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;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;check&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="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;tree_votes&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;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;git&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;HEAD&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="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="nf"&gt;git&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status_porcelain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;git&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;diff_stat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;git&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;--stat&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_sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&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;diff&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="nf"&gt;hexdigest&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;decide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_info&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;tree&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;failures&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;if&lt;/span&gt; &lt;span class="n"&gt;run_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exit_code&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;failures&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;command_failed&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;tree&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;failures&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;not_a_git_repo&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;tree&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_porcelain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;failures&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;working_tree_dirty&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;run_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stdout_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;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;run_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stderr_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;==&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;failures&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;empty_stdio&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;failures&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;run_info&lt;/span&gt; &lt;span class="o"&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;CMD&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;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tree_votes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;failures&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;run_info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;report&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;run&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;run_info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tree&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failures&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accepted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;failures&lt;/span&gt; &lt;span class="o"&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;REPORT&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;report&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="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;report&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;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;accepted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;1&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;Wrap it if you want a human summary:&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="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ACCEPT_CMD&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;ACCEPT_CMD&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="p"&gt; -m pytest -q&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
python3 accept_run.py
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
from pathlib import Path
rep = json.loads(Path("accept_report.json").read_text())
print("accepted:", rep["accepted"])
print("failures:", ", ".join(rep["failures"]) or "(none)")
print("head:", (rep["tree"]["head"] or "n/a")[:12])
print("dirty:", bool(rep["tree"]["status_porcelain"]))
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want dirt to fail closed? Keep &lt;code&gt;working_tree_dirty&lt;/code&gt;. Want intentional edits? Drop that flag. Make the policy explicit. Do not hide it in chat.&lt;/p&gt;

&lt;p&gt;Wrong gate, for contrast:&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;# Do not do this.&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;-qi&lt;/span&gt; &lt;span class="s1"&gt;'pass'&lt;/span&gt; agent.log&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;ship_it
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That grep is how myth 1 becomes a merge.&lt;/p&gt;

&lt;p&gt;A labeled false-green test also helps teach the team:&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;# labeled example: pytest can exit 0 while proving nothing
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_placeholder&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;All tests passed&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="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run pytest. Watch exit code 0. Then run the harness. Notice how little you proved besides zero. That is the lesson.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision table: what each signal proves
&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;Proves&lt;/th&gt;
&lt;th&gt;Does not prove&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agent said "passed"&lt;/td&gt;
&lt;td&gt;The model emitted text&lt;/td&gt;
&lt;td&gt;Any process ran&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exit code 0&lt;/td&gt;
&lt;td&gt;That argv did not fail&lt;/td&gt;
&lt;td&gt;The suite was complete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stdout hash&lt;/td&gt;
&lt;td&gt;Bytes were captured&lt;/td&gt;
&lt;td&gt;Bytes are correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git status --porcelain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Disk disagrees with HEAD&lt;/td&gt;
&lt;td&gt;The diff is good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git diff --stat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Size of the delta&lt;/td&gt;
&lt;td&gt;Intent of the delta&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Empty stdio&lt;/td&gt;
&lt;td&gt;You captured nothing&lt;/td&gt;
&lt;td&gt;Silence is success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Green on a scratch host&lt;/td&gt;
&lt;td&gt;One sample existed&lt;/td&gt;
&lt;td&gt;CI will match&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Argue with the table. Stop arguing with the chatbot.&lt;/p&gt;

&lt;h2&gt;
  
  
  A workflow that keeps the model off the grade
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Start from a known &lt;code&gt;HEAD&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Let the agent work on a branch.&lt;/li&gt;
&lt;li&gt;Ignore victory speech in the log.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;ACCEPT_CMD='python -m pytest -q' python3 accept_run.py&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Read &lt;code&gt;failures&lt;/code&gt; in &lt;code&gt;accept_report.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Only then inspect &lt;code&gt;git diff&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Need a cheap lab for that loop? Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode's free model access and free server option are the only product facts I rely on here. That pair is a scratch lab for rehearsing the harness. The model may draft commands. The harness still grades the run. Do not let the speaker mark its own exam.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to read the JSON without lying to yourself
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;accept_report.json&lt;/code&gt;. Read &lt;code&gt;failures&lt;/code&gt; first. Then read &lt;code&gt;exit_code&lt;/code&gt;. Then read &lt;code&gt;status_porcelain&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;accepted&lt;/code&gt; is true and you never looked at the diff, you skipped a vote. The script cannot review intent. You still can.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;empty_stdio&lt;/code&gt; is present, your command printed nothing. Silence is not success. Fix the command. Do not praise the agent.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;working_tree_dirty&lt;/code&gt; fires on every good session, change the policy. Record dirt as a flag. Review it. Do not hide policy in a prompt.&lt;/p&gt;

&lt;p&gt;Compare two reports only after you pin &lt;code&gt;ACCEPT_CMD&lt;/code&gt;. Different argv means different contracts. Hash mismatches then prove nothing useful.&lt;/p&gt;

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

&lt;p&gt;This harness is small on purpose. It is not a security scanner. It is not a flake oracle.&lt;/p&gt;

&lt;p&gt;It will miss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tests skipped by a bad glob&lt;/li&gt;
&lt;li&gt;mocks that hide production failures&lt;/li&gt;
&lt;li&gt;secrets echoed into captured logs&lt;/li&gt;
&lt;li&gt;hashes that churn because of timestamps&lt;/li&gt;
&lt;li&gt;tools installed outside the working tree&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nondeterministic stdout will fight hash comparison. Pin seeds. Pin locale. Or drop cross-host hash equality. Keep exit code plus git votes.&lt;/p&gt;

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

&lt;p&gt;Skip this if you need a signed audit trail. Skip this if tests cannot run headless. Skip this if a shared host would see real secrets.&lt;/p&gt;

&lt;p&gt;Do not copy &lt;code&gt;.env&lt;/code&gt; onto a free server. Do not call a lab gate production verification. If CI already stores these four votes, do not duplicate the ritual.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Can I parse the agent's English instead?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. English is not a schema. Emit JSON from your process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Is a screenshot of the run enough?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Did it show cwd, argv, and exit code? Usually it did not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: May the agent write the harness?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It can draft. You freeze the policy. Then you run it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What if a dirty tree is expected?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then dirty is a flag, not a failure. Review the diff as its own vote.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Does a scratch host make the result official?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. It makes the sample cheap. Official still means CI plus pins.&lt;/p&gt;

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

&lt;p&gt;I did not benchmark hosts. I did not name models. I did not promise quotas, hardware, or uptime.&lt;/p&gt;

&lt;p&gt;A free server can vanish. A free model can be wrong. Your harness must remain copyable.&lt;/p&gt;

&lt;p&gt;Remove every product mention and the method should still stand. If it does not, I wrote a brochure. I tried not to.&lt;/p&gt;

&lt;p&gt;Try the harness on a throwaway branch. Paste a redacted &lt;code&gt;failures&lt;/code&gt; list if you want a second pair of eyes. Bring votes. Leave the victory speech behind.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>python</category>
      <category>devops</category>
    </item>
    <item>
      <title>A Successful Import Is Not a Runtime Receipt</title>
      <dc:creator>Jordan Huang</dc:creator>
      <pubDate>Fri, 18 Sep 2026 20:58:48 +0000</pubDate>
      <link>https://dev.to/gitlab_3188/a-successful-import-is-not-a-runtime-receipt-4ljm</link>
      <guid>https://dev.to/gitlab_3188/a-successful-import-is-not-a-runtime-receipt-4ljm</guid>
      <description>&lt;p&gt;Did the agent really install your dependencies today?&lt;br&gt;
I keep seeing the same false comfort in chats.&lt;br&gt;
An import works. Then someone ships. Then CI explodes.&lt;/p&gt;

&lt;p&gt;This is not a rant about model quality.&lt;br&gt;
It is a runtime-identity problem instead.&lt;br&gt;
The chat log is not &lt;code&gt;sys.executable&lt;/code&gt; and never was.&lt;/p&gt;

&lt;p&gt;I will knock down five claims I still hear.&lt;br&gt;
Then you get a receipt you can rerun.&lt;br&gt;
You will see no mystery scores or fake hardware lists.&lt;/p&gt;
&lt;h2&gt;
  
  
  What this piece refuses to do
&lt;/h2&gt;

&lt;p&gt;I will not quote a latency number I did not measure.&lt;br&gt;
I will not name models I cannot verify here.&lt;br&gt;
I will not treat a transcript as a build log.&lt;/p&gt;

&lt;p&gt;The scripts below are a proposed workflow.&lt;br&gt;
Label them that way in your own notes.&lt;br&gt;
Run them on the same host the agent used.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 1: A clean import means the package is pinned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim.&lt;/strong&gt; "It imported &lt;code&gt;httpx&lt;/code&gt;. We are fine."&lt;/p&gt;

&lt;p&gt;Really? What version loaded into memory?&lt;br&gt;
Which extra was present on disk?&lt;br&gt;
Which interpreter performed the import?&lt;/p&gt;

&lt;p&gt;An import only proves one module object exists.&lt;br&gt;
It does not prove a pin or a content hash.&lt;br&gt;
It also does not prove tomorrow's host will match.&lt;/p&gt;

&lt;p&gt;Ask one blunt question after every import.&lt;br&gt;
Which file recorded that wheel on disk?&lt;br&gt;
If the answer is the chat, you have theater.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corrected model.&lt;/strong&gt; An import is a smoke test.&lt;br&gt;
A lockfile is the actual install contract.&lt;br&gt;
Keep those two jobs in separate boxes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 2: The host Python is your project Python
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim.&lt;/strong&gt; "The loaner box already has Python. Skip the venv."&lt;/p&gt;

&lt;p&gt;System Python is only a bootstrap tool.&lt;br&gt;
It is not your project's runtime profile.&lt;br&gt;
&lt;code&gt;sys.prefix&lt;/code&gt; and &lt;code&gt;sys.base_prefix&lt;/code&gt; tell the truth.&lt;/p&gt;

&lt;p&gt;If those two strings match, you are not in a venv.&lt;br&gt;
The agent may still print a friendly "installed."&lt;br&gt;
That print is social talk rather than isolation.&lt;/p&gt;

&lt;p&gt;Run this on the machine the agent used:&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;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sys; print(sys.executable); print(sys.prefix); print(sys.base_prefix)"&lt;/span&gt;
&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; python3
&lt;span class="nb"&gt;type &lt;/span&gt;python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do those lines agree with each other?&lt;br&gt;
If they disagree, the agent guessed the binary.&lt;br&gt;
Guessing the binary is not a packaging step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corrected model.&lt;/strong&gt; The project owns the interpreter.&lt;br&gt;
The host only loans a bootstrap binary.&lt;br&gt;
Create a venv before any install command runs.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 3: "Already satisfied" means the right version
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim.&lt;/strong&gt; "pip said already satisfied. Stop nagging."&lt;/p&gt;

&lt;p&gt;That phrase only means a name was found.&lt;br&gt;
It may still be the wrong version.&lt;br&gt;
It may be a user-site install instead.&lt;br&gt;
It may be leftover cache from another job.&lt;/p&gt;

&lt;p&gt;Name match is not the same as version match.&lt;br&gt;
Version match is not the same as extra match.&lt;br&gt;
Extra match is still not a lockfile.&lt;/p&gt;

&lt;p&gt;Check the two sources that actually exist:&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 show httpx
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; pip freeze | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^httpx=='&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;show&lt;/code&gt; drifts from your lock, you drifted.&lt;br&gt;
If &lt;code&gt;freeze&lt;/code&gt; is empty, you queried the wrong Python.&lt;br&gt;
Wrong Python is the usual failure, not "pip is broken."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corrected model.&lt;/strong&gt; Satisfied is only a cache message.&lt;br&gt;
It is not a reproducibility statement at all.&lt;br&gt;
Diff &lt;code&gt;pip freeze&lt;/code&gt; against the lock every session.&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 4: The chat remembered the virtualenv
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim.&lt;/strong&gt; "We activated &lt;code&gt;.venv&lt;/code&gt; two prompts ago."&lt;/p&gt;

&lt;p&gt;Did we? In which process, exactly?&lt;br&gt;
Shell state dies with the process that created it.&lt;br&gt;
A new command often gets a brand new shell.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;VIRTUAL_ENV&lt;/code&gt; is an environment variable.&lt;br&gt;
It is not model memory in the transcript.&lt;br&gt;
The transcript cannot export it for you.&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;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VIRTUAL_ENV&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;unset&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
.venv/bin/python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sys; print(sys.executable); print(sys.prefix != sys.base_prefix)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the first line prints &lt;code&gt;unset&lt;/code&gt;, activation died.&lt;br&gt;
If the second binary is missing, no venv exists.&lt;br&gt;
If the boolean is false, you are on host Python again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corrected model.&lt;/strong&gt; Activation is strictly per process.&lt;br&gt;
Re-export it in every command block you run.&lt;br&gt;
Or call &lt;code&gt;.venv/bin/python&lt;/code&gt; by absolute path.&lt;/p&gt;

&lt;p&gt;Absolute path beats folklore every time.&lt;br&gt;
Folklore is how agents "forget" the venv.&lt;br&gt;
Do you still trust a sentence that says "activated"?&lt;/p&gt;
&lt;h2&gt;
  
  
  Myth 5: Cheap rehearsal means you can skip the receipt
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim.&lt;/strong&gt; "It is only a loaner host. Why record it?"&lt;/p&gt;

&lt;p&gt;Because loaner hosts are where drift hides.&lt;br&gt;
A free model can still emit unpinned &lt;code&gt;pip install&lt;/code&gt;.&lt;br&gt;
A free server can still pick another default Python.&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 reach for MonkeyCode when a free model and a free server belong in the same rehearsal loop. That pairing is handy for trying the receipt without burning a laptop. It does not pin wheels for you, and it does not turn a chat into CI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corrected model.&lt;/strong&gt; Cost and identity are different questions.&lt;br&gt;
Cheap rehearsal still needs a written receipt.&lt;br&gt;
Free does not mean the runtime goes unrecorded.&lt;/p&gt;
&lt;h2&gt;
  
  
  The artifact: write a runtime receipt
&lt;/h2&gt;

&lt;p&gt;Here is a small proposed checker.&lt;br&gt;
I am not publishing timings for it.&lt;br&gt;
Copy it. Run it. Keep the JSON.&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;Write runtime-receipt.json for the current interpreter.

Proposed workflow. Run it yourself before you trust it.
&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;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;os&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;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;shutil&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;which&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sha256_if_exists&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;str&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;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;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&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;digest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&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_bytes&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;digest&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;pip_freeze_lines&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;completed&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="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;executable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-m&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;pip&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;freeze&lt;/span&gt;&lt;span class="sh"&gt;"&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;False&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="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&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;ERROR:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&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="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;completed&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;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;line&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="n"&gt;root&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;cwd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;receipt&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;cwd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&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;resolve&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sys_executable&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="n"&gt;executable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sys_version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&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="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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sys_prefix&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="n"&gt;prefix&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_prefix&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="n"&gt;base_prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in_venv&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="n"&gt;prefix&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;base_prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;which_python3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;which&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python3&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;which_python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;which&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VIRTUAL_ENV&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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;VIRTUAL_ENV&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;PATH_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;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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;PATH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pathsep&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requirements.txt&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_if_exists&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requirements.txt&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;requirements.lock&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_if_exists&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requirements.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;pyproject.toml&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_if_exists&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pyproject.toml&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;pip_freeze&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;pip_freeze_lines&lt;/span&gt;&lt;span class="p"&gt;(),&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;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;runtime-receipt.json&lt;/span&gt;&lt;span class="sh"&gt;"&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;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;receipt&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="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="n"&gt;out&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in_venv&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;warning: not in a virtualenv&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;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;Need a tiny test beside it?&lt;br&gt;
Yes. Assert identity, not vibes.&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;# test_runtime_receipt.py
&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_receipt_file_exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&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;runtime-receipt.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;is_file&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_receipt_matches_running_interpreter&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="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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;runtime-receipt.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;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;assert&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sys_executable&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;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executable&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in_venv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prefix&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;base_prefix&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_pin_file_was_recorded&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="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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;runtime-receipt.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;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;has_req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requirements.txt&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="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;has_lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requirements.lock&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="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;has_pyproject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pyproject.toml&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="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;has_req&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;has_lock&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;has_pyproject&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What the JSON keys actually mean
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sys_executable&lt;/code&gt;: the binary that actually ran.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;in_venv&lt;/code&gt;: prefix inequality, not a vibe check.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;which_python3&lt;/code&gt;: whatever &lt;code&gt;PATH&lt;/code&gt; says right now.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;VIRTUAL_ENV&lt;/code&gt;: empty means activation did not survive.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pip_freeze&lt;/code&gt;: the live view of site-packages.&lt;/li&gt;
&lt;li&gt;file hashes: did the pin files change under you?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If &lt;code&gt;sys_executable&lt;/code&gt; and &lt;code&gt;which_python3&lt;/code&gt; disagree, stop.&lt;br&gt;
You are debugging the wrong interpreter now.&lt;br&gt;
Everything after that disagreement is noise.&lt;/p&gt;
&lt;h3&gt;
  
  
  Proposed commands
&lt;/h3&gt;

&lt;p&gt;Do not paste this blindly into production.&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; venv .venv
.venv/bin/python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; pip
.venv/bin/python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
.venv/bin/python runtime_receipt.py
.venv/bin/python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest test_runtime_receipt.py &lt;span class="nt"&gt;-q&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What should you commit after that?&lt;br&gt;
Commit &lt;code&gt;runtime_receipt.py&lt;/code&gt; and the tests.&lt;br&gt;
Commit the lockfile you actually used.&lt;br&gt;
Treat &lt;code&gt;runtime-receipt.json&lt;/code&gt; as session evidence.&lt;br&gt;
Redact paths if they leak a username.&lt;/p&gt;

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

&lt;p&gt;Use this before you trust an agent install.&lt;/p&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;Write a receipt?&lt;/th&gt;
&lt;th&gt;Free model + free server okay?&lt;/th&gt;
&lt;th&gt;Ship from that host?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Exploring a script&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes, as rehearsal&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pinning dependencies&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes, if you copy freeze out&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App tests after pins&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Only if the receipt matches CI&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production release&lt;/td&gt;
&lt;td&gt;Yes, inside your CI&lt;/td&gt;
&lt;td&gt;Not as the release runner&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets in the tree&lt;/td&gt;
&lt;td&gt;Yes, on a machine you own&lt;/td&gt;
&lt;td&gt;Avoid shared loaner hosts&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the last column again, slowly.&lt;br&gt;
Rehearsal is not a release channel.&lt;br&gt;
That is the whole table.&lt;/p&gt;

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

&lt;p&gt;Skip this workflow if you need a full SBOM.&lt;br&gt;
Skip it if you cannot run Python at all.&lt;br&gt;
Skip it if the host must never see your lockfile.&lt;br&gt;
Skip it if legal wants an attested builder.&lt;/p&gt;

&lt;p&gt;Also skip the shared free server when secrets sit in &lt;code&gt;.env&lt;/code&gt;.&lt;br&gt;
A receipt will not unsay a leaked token.&lt;br&gt;
Keep secrets off loaner disks on purpose.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;pip freeze&lt;/code&gt; is incomplete for some extras.&lt;br&gt;
It does not replace &lt;code&gt;uv lock&lt;/code&gt; or Poetry.&lt;br&gt;
It does not understand Conda environments.&lt;br&gt;
It does not prove your app tests passed.&lt;br&gt;
It can record absolute paths. Redact them.&lt;/p&gt;

&lt;p&gt;The venv check uses prefix inequality.&lt;br&gt;
That is the usual CPython signal today.&lt;br&gt;
It is not a law of every embedding.&lt;/p&gt;

&lt;p&gt;I also do not claim the receipt is tamper proof.&lt;br&gt;
It is a snapshot you can diff later.&lt;br&gt;
Diffs beat vibes. That is enough for rehearsal.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ, compressed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Did the import pass?&lt;/strong&gt; Then you have smoke, not a pin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Did pip look happy?&lt;/strong&gt; Then you have a cache message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Did the chat mention a venv?&lt;/strong&gt; Then you have a story.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do you have JSON for &lt;code&gt;sys.executable&lt;/code&gt;?&lt;/strong&gt; Then we can talk about installs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stop asking "did it import?" as the finish line.&lt;br&gt;
Start asking "which interpreter, which freeze, which file?"&lt;br&gt;
Three answers. One JSON. Then the debate is real.&lt;/p&gt;

&lt;p&gt;The agent can help type the commands.&lt;br&gt;
You still own the receipt after that.&lt;br&gt;
If those two roles blur, you are back in myth land.&lt;/p&gt;

&lt;p&gt;If you rehearse on a free server, commit the receipt.&lt;br&gt;
That is the only ask.&lt;/p&gt;

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