The transcript closed like a solved ticket. A coding agent printed git commit -m "fix retry budget", then a 7-character SHA, then a test line that ended in 3 passed. I shut the laptop. The next morning git log -1 --oneline on the same clone still showed Tuesday's hash. The SHA from the chat did not exist locally, not as a dangling commit and not in the reflog. It had been minted on a machine I did not own.
This is the apartment-key problem. The paperwork can be perfect while the door stays locked. Remote agent loops make that failure cheap, fast, and quiet.
Hour 0–2: freeze the tree before anyone else talks
The exercise was small on purpose. A local CLI named retryctl applied exponential backoff to HTTP 429s, and the bug was real: the sleep used attempt instead of attempt + 1, so the first retry waited zero milliseconds. I froze three numbers before the agent touched anything.
git rev-parse HEAD > /tmp/local.sha
git status --porcelain > /tmp/local.dirty
python3 - <<'PY'
import hashlib, pathlib
p = pathlib.Path("retryctl.py")
print(hashlib.sha256(p.read_bytes()).hexdigest()[:16])
PY
> /tmp/local.filehash
cat /tmp/local.sha /tmp/local.dirty /tmp/local.filehash
The dirty list was empty. The file hash was the control sample. Anything the agent claimed later had to move at least one of those values, or the session was a diary entry, not a delivery.
I did not run the loop on the laptop. A remote scratch box is useful when you want the agent to install packages, fail loudly, and retry without polluting a workstation. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I pointed the session at MonkeyCode because it currently offers free model access and a free server option, which is enough to rehearse the receipt workflow without inventing a hardware budget. The product is not the test. The receipt is.
Hour 2–6: a SHA is a rumor until you can cat-file it
The agent cloned, edited, committed, and ran pytest. The chat even echoed a commit object. I treated that echo as hearsay until it could be replayed on this disk.
Here is the starting fixture. It is labeled as a lab bug, not production advice.
# retryctl.py — starting fixture
import time
def backoff_seconds(attempt: int, base: float = 0.25, cap: float = 8.0) -> float:
# off-by-one: attempt 0 sleeps 0s
return min(cap, base * (2 ** attempt))
def retry(op, attempts: int = 4):
last = None
for i in range(attempts):
try:
return op()
except Exception as e:
last = e
time.sleep(backoff_seconds(i))
raise last
A correct patch would change the exponent to attempt + 1 or start the loop at 1. Either is fine. Neither matters if retryctl.py on the laptop does not change.
I asked for a machine-readable receipt instead of a paragraph. The schema was boring on purpose.
{
"sandbox_head_before": "UNKNOWN",
"sandbox_head_after": "UNKNOWN",
"files_changed": [],
"commands": [],
"pytest_exit": null,
"collected": null,
"bundle_sha256": null
}
Hour 5 was the first break. The model filled sandbox_head_after with a plausible hex string and wrote files_changed as a prose sentence. JSON that cannot be parsed is not a receipt. It is a vibe with punctuation.
The second attempt produced valid JSON and a bundle_sha256 for a git bundle it said it had written to /tmp/retryctl.bundle. There was still no bundle on my machine. The conversation had completed a commit ceremony in a room I could not enter.
Hour 6–14: retrieve is a step, not a courtesy
Remote execution without a pull-back is how you get a green story and an untouched working tree. The missing piece was not a cleverer prompt. It was transport.
I added an export command to the runbook, to be executed in the sandbox and only then fetched.
# sandbox side
git bundle create /tmp/retryctl.bundle HEAD~5..HEAD
sha256sum /tmp/retryctl.bundle
# laptop side — only after the file actually arrives
test -s /tmp/retryctl.bundle || { echo "no bundle"; exit 1; }
git bundle verify /tmp/retryctl.bundle
git fetch /tmp/retryctl.bundle +HEAD:refs/remotes/sandbox/head
git log --oneline sandbox/head
What broke was almost mundane. The sandbox path was /workspace/retryctl and the local clone was ~/src/retryctl. The agent ran git bundle create from /tmp against a repository that was not the current directory. git bundle verify on the laptop rejected the file as not a bundle. The bytes were a pytest log renamed with a .bundle suffix.
That is a useful failure. A lying filename is cheaper to catch than a lying SHA pasted into chat. The second break was subtler. After a real bundle arrived, git fetch created sandbox/head while laptop HEAD stayed put. The agent had committed. I had not merged. Anyone who only read the transcript would have called the job done.
Clock drift showed up once as well. The sandbox GIT_AUTHOR_DATE was several hours ahead of the laptop, which made git log --since=4.hours.ago look empty after a fetch. Time is not a receipt. Parent SHA is.
Hour 14–28: a verifier that fails closed
I stopped reading the chat for proof. The laptop became the only judge. The script below is the artifact from the window. Run it locally after any remote agent session, free server or otherwise.
#!/usr/bin/env python3
"""Fail closed: a remote coding receipt must move local git state."""
from __future__ import annotations
import hashlib
import json
import subprocess
import sys
from pathlib import Path
REQUIRED = (
"sandbox_head_after",
"files_changed",
"pytest_exit",
"collected",
"bundle_sha256",
)
def sh(*args: str) -> str:
r = subprocess.run(args, check=True, capture_output=True, text=True)
return r.stdout.strip()
def main() -> int:
receipt = json.loads(Path(sys.argv[1]).read_text())
before = Path(sys.argv[2]).read_text().strip()
bundle = Path(sys.argv[3])
for key in REQUIRED:
if receipt.get(key) in (None, "", [], "UNKNOWN"):
print(f"receipt missing {key}")
return 2
digest = hashlib.sha256(bundle.read_bytes()).hexdigest()
if digest != receipt["bundle_sha256"]:
print("bundle hash mismatch")
return 3
sh("git", "bundle", "verify", str(bundle))
sh("git", "fetch", str(bundle), "+HEAD:refs/tmp/sandbox-head")
after = sh("git", "rev-parse", "refs/tmp/sandbox-head")
if after == before:
print("sandbox head equals preflight SHA; no work landed")
return 4
if after != receipt["sandbox_head_after"]:
print("claimed SHA does not match bundle")
return 5
names = sh("git", "diff", "--name-only", before, after).splitlines()
if sorted(receipt["files_changed"]) != sorted(names):
print("files_changed does not match bundle diff")
return 6
if int(receipt["pytest_exit"]) != 0:
print("remote pytest did not pass; refusing merge")
return 7
if int(receipt["collected"]) < 1:
print("zero tests collected; green is not evidence")
return 8
print(f"receipt ok: {before[:7]} -> {after[:7]}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Usage is three files and an exit code.
python3 verify_receipt.py receipt.json /tmp/local.sha /tmp/retryctl.bundle
echo $?
Exit 4 was the common case in this window: the bundle verified and still pointed at the preflight SHA. The agent had described a patch without recording one. Exit 3 showed up when the chat hash was computed on pretty-printed JSON instead of the bundle bytes. Words are not hashes. A hex string in a paragraph is costume jewelry until sha256sum agrees.
I also pinned two tests so "pytest passed" could not mean "collected 0."
# test_retryctl.py
from retryctl import backoff_seconds
def test_first_retry_is_nonzero():
assert backoff_seconds(0) > 0
def test_grows_until_cap():
assert backoff_seconds(1) < backoff_seconds(3)
Collection count landed in the receipt in hour 22, not hour 14. The verifier had already said the bundle was fine, and the function still slept zero seconds. The patch had touched README.md. A green remote run that never collected the assertion is the sibling of an empty working tree. Same family of bug, different room.
Hour 28–48: what broke, what I would repeat
The useful loop was not "better prompting." It was a closed circuit. Freeze local state, let the remote agent work, demand a bundle plus JSON, verify on the laptop, merge only if the hashes move in the same direction as the tests.
A free model is a way to retry that circuit without treating every failed JSON parse as a billing event. A free server is a way to keep messy installs off the workstation. Neither replaces the retrieve step. If the transport is missing, a cheaper loop just accumulates confident fiction faster.
I would repeat the preflight hashes. I would repeat git bundle over "paste the diff into chat." Chat diffs lose file mode, drop binaries, and invite the model to pretty-print a patch that no longer applies. I would repeat fail-closed exit codes. I would not repeat trusting a SHA that cannot be git cat-file -t'd on this machine.
Who should not use this approach is clearer than who should. If the repo is public-shaped, secrets are not in the tree, and a wrong patch that never merges is acceptable, a free remote server is a reasonable anvil. If the tree holds credentials, customer dumps, or signed release tags, do not ship the work to a disk you do not control. Free shared compute is not an air gap. It is convenience with someone else's filesystem and someone else's clock.
The 48 hours did not produce a productivity percentage, a latency chart, or a model ranking. They produced five verifier exit codes, one renamed pytest log, and one patch that finally moved retryctl.py on the laptop. That is the only metric this notebook will claim.
If you want a remote session to throw against the same receipt script, MonkeyCode's free model access and free server option are a workable place to rehearse. The script stays useful if you never open that tab.
Top comments (0)