Did your coding agent just call a tool? Did that tool wrapper return HTTP 200?
Someone will treat that code as done. I refuse that shortcut on every review.
A green tool response is only a transport fact. It still does not close the ticket.
Why this FAQ exists
Teams repeat the same claims after a successful call. They sound sharp. They fold when you keep a receipt.
This piece is about the tool boundary. Not about chat tone. Not about a pretty session.
You ran a command. The wrapper said OK. What did you actually prove?
Where the receipts run
I need a cheap box to burn bad assumptions. Free model access helps that job. A free server option helps it too.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode is one place I can pair a free model with a free server. I do not treat that pair as an SLA. I treat it as a scratch box for receipts.
Use another stack if you want. The receipt format still stands. Strip the product name. Keep the checks.
The artifact: a tool-call receipt
I log every tool call as JSON. Narrative comes second. Raw payload comes first.
Then I ask one rude question. Does the agent's summary match the payload?
Treat this as a proposed workflow. Label it unproven until you run it. Then trust the file, not the vibe.
Receipt schema (proposed)
Keep fields short. You should grep them later.
{
"ts": "2026-09-09T12:00:00Z",
"tool": "run_shell",
"argv": ["git", "status", "--porcelain"],
"cwd": "/work/app",
"exit_code": 0,
"http_status": 200,
"stdout_sha256": "",
"stderr_sha256": "",
"stdout_bytes": 0,
"truncated": false,
"agent_claim": "",
"claim_match": "unknown"
}
Four facts matter most. argv. cwd. exit code. hashes.
If those four are missing, you have theater. Do you still want to merge?
Capture helper (proposed)
Hash what you saw. Not what the agent restated.
#!/usr/bin/env bash
set -euo pipefail
# proposed helper — review before you run it
out="$(mktemp)"
err="$(mktemp)"
set +e
"$@" >"$out" 2>"$err"
code=$?
set -e
stdout_sha="$(sha256sum "$out" | awk '{print $1}')"
stderr_sha="$(sha256sum "$err" | awk '{print $1}')"
bytes="$(wc -c < "$out" | tr -d ' ')"
printf 'exit_code=%s\n' "$code"
printf 'stdout_sha256=%s\n' "$stdout_sha"
printf 'stderr_sha256=%s\n' "$stderr_sha"
printf 'stdout_bytes=%s\n' "$bytes"
echo '----- stdout head -----'
head -c 2000 "$out"
echo
echo '----- stderr head -----'
head -c 500 "$err"
echo
Pass the real command after the script name. Keep the hashes beside the patch.
chmod +x receipt.sh
./receipt.sh git status --porcelain
./receipt.sh python -m pytest -q
Now you have bytes. The chat has a story. Diff them.
Tiny claim checker (proposed)
This script calls no model. You paste both sides.
# proposed: claim_check.py
import json
import sys
receipt = json.load(open(sys.argv[1], encoding="utf-8"))
claim = open(sys.argv[2], encoding="utf-8").read().strip().lower()
needles = [
"nothing to commit",
"clean",
"tests passed",
"all green",
"no changes",
"build succeeded",
]
hits = [n for n in needles if n in claim]
print("claim_needles:", hits)
print("exit_code:", receipt.get("exit_code"))
print("truncated:", receipt.get("truncated"))
print("stdout_bytes:", receipt.get("stdout_bytes"))
print("http_status:", receipt.get("http_status"))
if receipt.get("http_status") == 200 and receipt.get("exit_code") not in (0, None):
print("WARN: HTTP 200 with a dirty exit_code")
if receipt.get("exit_code") not in (0, None) and "passed" in claim:
print("FAIL: claim says passed, exit_code is not 0")
sys.exit(2)
if int(receipt.get("stdout_bytes") or 0) == 0 and "passed" in claim:
print("FAIL: empty stdout, claim still boasts")
sys.exit(2)
print("REVIEW: hashes beat adjectives")
I do not auto-merge on this script. It only fails loud lies. Humans still read stdout.
FAQ: myths a 200 will sell you
Myth 1: 200 means the agent understood the repo
Did the HTTP layer smile? Good. That is a handler plus a socket.
Understanding would name a build graph. Or a failing test in stdout.
Claim you hear: "It called the tool, so it gets the repo."
Evidence that survives: argv as sent, cwd, exit code, hashes.
Corrected model: 200 means the tool ran. It does not mean the agent read the system.
Want a quick probe?
# proposed probes — pick what your repo actually uses
git rev-parse --show-toplevel
git ls-files | wc -l
test -f package-lock.json && echo lockfile_present
test -f go.mod && go list -m
test -f pyproject.toml && echo pyproject_present
If the receipt only shows a wrapper ping, nobody understood anything. They connected.
Myth 2: ls is a design review
Agents love listing files. Humans love that listing. Why?
A file name is not a Makefile target. A folder is not a lockfile.
Claim you hear: "It listed src, so the change fits."
Evidence that survives: a parsed manifest, a dry-run build, a real test command.
Corrected model: listing is inventory. Inventory is not architecture.
# proposed: force a graph, not a tour
test -f Makefile && make -n
test -f package.json && node -e "console.log(Object.keys(require('./package.json').scripts||{}))"
test -f go.mod && go test ./... -count=0
test -f pyproject.toml && python -m pytest --collect-only -q
If the receipt only shows ls, the review did not happen. Would you accept that from a junior?
Myth 3: a failed tool stops the agent
Who promised that brake? The system prompt? A blog post?
Many loops swallow errors. They retry. They invent a close-enough file.
Claim you hear: "If the tool fails, it will stop."
Evidence that survives: a wrapper that halts on exit_code != 0.
Corrected model: failure is data. It is not a brake unless you wire one.
policy (proposed):
- if exit_code != 0: halt and show stderr
- do not draft a patch
- do not claim a workaround unless a human types it
- do not retry more than once without a new receipt
Put that in the wrapper. Do not trust the model to remember it. Memory is not a mutex.
Myth 4: free model plus free server means the tool stayed private
Free is a price. It is not a network diagram.
Did the prompt include .env? Did the tool dump secrets into logs?
Claim you hear: "It is free, so it stayed on my box."
Evidence that survives: a tripwire string that never appears in dumps.
Corrected model: free capacity is interruptible and inspectable. It is not air-gapped by default.
# proposed secret tripwire — throwaway clone only
# do not use production values
printf 'TRIPWIRE_SECRET=do-not-leak\n' > /tmp/fake.env
# after the session:
rg -n "TRIPWIRE_SECRET" receipt.jsonl claim.txt session.md || true
If that string shows up in a prompt dump, you learned something. Cheap lesson. Ugly one.
MonkeyCode's free model access and free server option are useful for this drill. They are not a compliance stamp. No stack gets a stamp from a blog post.
Myth 5: the agent's summary is the tool output
This one bites seniors. The model paraphrases. It drops the ugly line.
"Tests passed" might hide a pile of skips. "Clean tree" might hide untracked build junk.
Claim you hear: "It said the tests passed, so they passed."
Evidence that survives: exit code plus a stdout hash plus a head dump.
Corrected model: summaries are press releases. Receipts are source.
# proposed: bind the claim to a hash, not a vibe
printf 'claim=%s\n' "tests passed"
printf 'stdout_sha256=%s\n' "$stdout_sha"
printf 'exit_code=%s\n' "$code"
If the hash changes, the claim is stale. Re-run. Do not negotiate with a paragraph.
Myth 6: parallel tool calls are independent
Two 200s look clean. Shared cwd is not clean.
One call writes tmp.json. Another reads it. Order is a heisenbug.
Claim you hear: "Both tools succeeded, so the patch is consistent."
Evidence that survives: a serial receipt, or a lock around the working tree.
Corrected model: parallel success is still one disk. Sequence it when files overlap.
# proposed: refuse overlapping writers
flock /tmp/agent-cwd.lock ./receipt.sh python -m pytest -q
If you cannot name the shared files, you cannot name the race. So don't ship it.
Decision table
Use this before you merge. Print it. Argue with it.
| Observation | You proved | You did not prove | Next move |
|---|---|---|---|
| Tool HTTP 200 | The wrapper answered | The task is done | Read exit_code
|
exit_code 0 |
Process finished clean | Tests exist or passed | Run the real test command |
ls succeeded |
Names exist | Graph, deps, scripts | Parse a lockfile or Makefile |
| Agent says "all green" | A sentence was generated | CI is green | Compare hashes to claim.txt
|
| Free server still up | You got a slot | Tomorrow's slot | Treat capacity as interruptible |
Prompt omitted .env
|
You tried | Nothing leaked | Search logs for tripwires |
| Two tools returned 200 | Two wrappers answered | No cwd race | Serialize writers |
No scores. No fake latency theater. Just branches.
A one-hour drill (proposed)
Do this on a throwaway repo. Not on prod. Not on customer data.
- Start a session on a free model. Use a free server if you have one.
- Ask the agent to check the repo and run tests.
- Capture every tool argv with
receipt.sh. - Save the agent's final paragraph as
claim.txt. - Run
python claim_check.py receipt.json claim.txt. - Fill the decision table. Write the next move in the ticket.
What I watch for, as a pattern, not as a benchmark:
- at least one claim will outrun the payload
-
lswill show up more than the real test runner - a 200 will hide a dirty
exit_code - someone will still want to merge
That someone is the myth. Are you that someone today?
Limitations
This receipt is not a security audit. It is a honesty check.
It will not catch a model that never calls tools. It will not catch a human who pastes secrets on purpose.
Hashes do not prove correctness. They prove you saw a specific byte string. Truncation still lies.
Who should not use this approach:
- anyone dumping customer data into a free prompt
- anyone who needs a contractual uptime target
- anyone replacing CI with a chat transcript
- anyone who will not read stdout because "the agent already did"
- anyone treating a free server as an air gap
If you need isolation, use a locked-down runner you control. If you need an SLA, pay for one and read the terms.
Free model access can go away. Free servers can go away. Design the workflow so a missing box is boring.
Corrected mental model
A tool call is a function invocation. Treat it like one.
You would not merge because curl returned 200 from /health. You would read the body. Why treat an agent with less care?
Four facts, every time:
- What argv ran
- Where it ran
- What it printed
- What the agent claimed it printed
If 3 and 4 disagree, the ticket stays open. Easy. Unpopular. Correct.
Wire the brake in your wrapper. Keep the receipt beside the patch. Leave the press release in the chat.
What I do next
I keep the receipt file next to the patch. Not in the transcript.
If you already have MonkeyCode free model access and a free server slot, run the drill once on a throwaway clone. Then delete the tripwire file.
No quota story. No urgency. One receipt that argues with the myth.
Top comments (0)