Have you ever merged because a scratch host looked green? I keep hearing that shortcut in agent threads. The command exited zero. The chat said done. So the patch must be real, right?
No. A scratch host is a rehearsal room. It is not CI. It is not production. It is not even your laptop.
This FAQ breaks five claims I still see. Each one has a cheaper check. Each one has a better mental model. Steal the receipt script. Then argue with evidence, not vibes.
Why this FAQ exists
Agent loops now jump between laptops and remote sandboxes. Free models draft the next command. A free server runs it. That loop is useful. It is also easy to misread.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. I treat MonkeyCode's free model access and free server option as a scratch loop. Draft there. Reproduce there. Do not merge from there.
Ask yourself one rude question. What exactly went green? A process? A prompt? A feeling?
Myth 1: Green on the scratch host means CI will be green
The claim: The remote command passed. Ship it.
What actually happened: One process exited. In one filesystem. With one PATH. With one Git SHA you may not have pinned.
CI is a different contract. Different image. Different secrets handling. Different network. Different time. Different user.
Corrected model: treat the sandbox as a cheap reproduction box. Copy the exact command. Copy the exit code. Copy the SHA. Then rerun that receipt locally. Then let real CI be the gate.
git rev-parse HEAD
python -V
which python
echo "$PWD"
Did those four lines match CI? If not, the green was local folklore.
Myth 2: The sandbox runtime is close enough to production
The claim: It is Linux. Python runs. Close enough.
What actually happened: You ran a interpreter. Not the interpreter. Patchlevel differs. OpenSSL differs. Locale differs. The default umask differs.
I do not need a vendor benchmark for this. I need sys.version and a platform tuple. That is enough to catch a lie.
python - <<'PY'
import platform, sys, os, hashlib, json
print(json.dumps({
"python": sys.version,
"platform": platform.platform(),
"cwd": os.getcwd(),
"uid": os.getuid() if hasattr(os, "getuid") else None,
}, indent=2))
PY
Corrected model: runtime identity is data. It is not a vibe. If the receipt drifts, the green result is not portable.
Who cares about patchlevels? Anyone shipping wheels. Anyone touching TLS. Anyone hashing files. You, probably.
Myth 3: The chat summary is the command log
The claim: The model recapped the run. That recap is the record.
What actually happened: Summaries drop flags. They drop cwd. They drop env. They drop the second command that actually failed.
Would you accept a chat log as dmesg? Then do not accept it as make test.
Corrected model: the artifact is a receipt file. The chat is commentary. Commentary can be wrong. The file can be hashed.
Write the receipt yourself. Do not ask the model to remember it later.
mkdir -p .receipts
{
echo "sha=$(git rev-parse HEAD)"
echo "status=$(git status --porcelain | wc -l)"
echo "cmd=pytest -q tests/test_api.py"
} | tee .receipts/scratch.env
Dirty tree? Then the green command did not test the commit. It tested a soup.
Myth 4: Free-model drafts skip the same review
The claim: It is only a draft. Review can wait.
What actually happened: Drafts still touch files. Drafts still run installs. Drafts still leave caches. A cheap model can still mutate the tree.
Price of the tokens is not the risk. The working tree is the risk. Why would a free draft get a weaker diff review?
Corrected model: cheaper generation means more receipts. Not fewer. You will iterate more. You will forget more. Pin the SHA anyway.
I still read the diff like a hostile patch. Branch name does not make it safe. Model tier does not make it safe.
Myth 5: A new sandbox session is a clean universe
The claim: New session. Fresh machine. No leftovers.
What actually happened: Tool caches survive. Virtualenvs survive. TMPDIR survives. Your last export survives if the host reuses a workspace.
Did you print env | sort? Did you hash site-packages? If not, you guessed.
env | sort > .receipts/env.scratch.txt
find . -name '*.pyc' | wc -l
ls -la .venv 2>/dev/null || true
Corrected model: isolation is a property you measure. It is not a product slogan. Assume leftovers until the receipt says otherwise.
The artifact: a run receipt you can diff
Here is a small workflow I actually want in the repo. It is a script, not a speech. Label it as a local tool. Run it on the scratch host. Run it on your laptop. Diff the JSON.
#!/usr/bin/env python3
"""run_receipt.py — capture enough context to distrust a green exit."""
from __future__ import annotations
import hashlib, json, os, platform, subprocess, sys, time
from pathlib import Path
def sh(args: list[str]) -> str:
p = subprocess.run(args, text=True, capture_output=True)
return (p.stdout or "").strip()
def file_hash(path: Path) -> str | None:
if not path.is_file():
return None
h = hashlib.sha256()
h.update(path.read_bytes())
return h.hexdigest()[:16]
def main() -> int:
cmd = sys.argv[1:]
if not cmd:
print("usage: run_receipt.py -- <command>", file=sys.stderr)
return 2
started = time.time()
proc = subprocess.run(cmd)
receipt = {
"argv": cmd,
"exit_code": proc.returncode,
"elapsed_sec": round(time.time() - started, 3),
"cwd": os.getcwd(),
"python": sys.version.split()[0],
"platform": platform.platform(),
"git_head": sh(["git", "rev-parse", "HEAD"]),
"git_porcelain": sh(["git", "status", "--porcelain"]),
"lock_hash": file_hash(Path("uv.lock")) or file_hash(Path("poetry.lock"))
or file_hash(Path("package-lock.json")),
}
out = Path(".receipts")
out.mkdir(exist_ok=True)
name = out / f"receipt-{receipt['git_head'][:8]}-{int(started)}.json"
name.write_text(json.dumps(receipt, indent=2) + "\n")
print(name)
return proc.returncode
if __name__ == "__main__":
raise SystemExit(main())
Run it the same way in both places:
python run_receipt.py -- python -m compileall src
diff -u .receipts/receipt-local.json .receipts/receipt-scratch.json
Look at git_porcelain first. Then python. Then lock_hash. Then exit_code. A matching exit with a drifting lock is not a match. It is a coincidence.
Decision table
Use this when someone says "but it passed over there."
| Signal | Trust it as | Do not treat it as |
|---|---|---|
| Exit 0 on scratch host | Reproduction clue | Merge gate |
| Free-model recap of the run | Hypothesis | Command log |
Matching git_head + empty porcelain |
Same tree | Same runtime |
Matching lock_hash
|
Same declared deps | Same compiled wheels |
| Real CI on the default branch | Release signal | Proof the sandbox was clean |
If two rows disagree, believe the colder one. Receipts beat chat. CI beats receipts. Production still beats CI.
A 15-minute drill
Do this on the next agent patch. No extra platform required.
- Create a throwaway branch from a known SHA.
- Run
run_receipt.pyon the failing command. - Reproduce once on a scratch host with the same argv.
- Diff the two JSON files.
- Only then let the model propose a patch.
- Re-run the receipt. Refuse a pass on a dirty tree.
That is the whole loop. Short. Mean. Repeatable.
What if the model wants to "just install a missing package"? Stop. That is a runtime drift. Record it. Do not hide it inside a green exit.
Limitations
This receipt is not a hermetic build. It does not freeze the kernel. It does not freeze CPU flags. It does not prove determinism. It only kills the loudest lies.
It also will not help if you never pin a SHA. Garbage in. JSON out.
Skip this approach when:
- The tree holds regulated data the scratch host must not see.
- You need bit-identical artifacts, not just matching exits.
- The command is interactive and has no argv.
- You cannot write
.receipts/because the host is read-only.
Those cases need a real image pipeline. Not a FAQ. Not a chat model.
The mental model I want you to keep
A free model is a fast draft engine. A free server is a cheap rehearsal room. Neither one owns main.
Green means one process ended. Portable means two receipts match. Shippable means CI agrees after that.
Still tempted to merge from the sandbox log? Ask the only question that matters. Where is the receipt?
If you already keep scratch hosts around, drop run_receipt.py in the repo and diff one failure this week. That file will argue louder than another confident recap.
Top comments (0)