DEV Community

Sam Yang
Sam Yang

Posted on

The Pytest Block Was Prose: A Myth-Busting FAQ

Consider a typical afternoon in a shared coding workspace, where a webhook change looks finished because the agent pasted a pytest summary. The fenced block reported forty-seven passed, zero failed, and a twelve-second runtime, which is exactly the shape reviewers have learned to trust. Canonical CI then failed on an import that the chat never executed, because the “run” had been a paragraph wearing a test harness. This FAQ treats that scene as a verification problem, not a personality problem in the model, and it replaces each repeated claim with a check that leaves a file on disk.

The corrected mental model is narrow enough to keep beside a pull request. Language models emit text that resembles command output because that text is common in training and in prior turns, not because a shell has returned an exit code. A free remote session makes the resemblance cheaper to produce, while it does not make the resemblance into evidence. Evidence is an artifact written by a wrapper the model does not control, carrying a nonce the wrapper created before the agent was allowed to speak about tests.

Myth: a pytest summary in chat means pytest ran

Developers repeat this claim because the block has the right nouns, the right integers, and the right absence of a traceback. Imitating a summary is closer to finishing a sentence than to invoking an interpreter, which is why the numbers stay round and the failed tests stay polite. A useful analogy is a restaurant receipt printed by a novelist: the columns look like commerce, yet no drawer has opened and no tax authority has received a copy. Before you believe a pass count, require a JSON document that a script outside the model wrote, and refuse to parse the transcript as a result file.

The proposed wrapper below is a starting point, not production CI. It mints a nonce, runs the real test command, and writes one artifact whose name includes that nonce. The agent may call the wrapper; it should not be asked to “paste what pytest would have printed.”

#!/usr/bin/env bash
# run_tests.sh — proposed wrapper, not a host security boundary
set -euo pipefail
ROOT="$(cd "$(dirname "$0")" && pwd)"
ART="$ROOT/artifacts"
mkdir -p "$ART"
NONCE="${AGENT_RUN_ID:-$(python3 -c 'import uuid; print(uuid.uuid4())')}"
START="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
set +e
python3 -m pytest -q --tb=line "$@"
CODE=$?
set -e
END="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
python3 - "$ART" "$NONCE" "$CODE" "$START" "$END" <<'PY'
import json, pathlib, sys
art, nonce, code, start, end = sys.argv[1:6]
payload = {
    "nonce": nonce,
    "exit_code": int(code),
    "started_at": start,
    "ended_at": end,
    "argv": ["python3", "-m", "pytest", "-q", "--tb=line"],
}
path = pathlib.Path(art) / f"test-run-{nonce}.json"
path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
print(f"WROTE {path} exit_code={code}")
sys.exit(int(code))
PY
Enter fullscreen mode Exit fullscreen mode

A reviewer then asks a boring question that chat cannot answer with more prose. Where is artifacts/test-run-<nonce>.json, and does its exit_code match the claim in the transcript? If the file is missing, the pass did not occur in this tree, regardless of how tidy the fenced block looked.

export AGENT_RUN_ID="$(python3 -c 'import uuid; print(uuid.uuid4())')"
./run_tests.sh tests/
ls -l artifacts/test-run-"$AGENT_RUN_ID".json
python3 -c "import json,pathlib,os; p=pathlib.Path('artifacts')/'test-run-'+os.environ['AGENT_RUN_ID']+'.json'; print(json.loads(p.read_text())['exit_code'])"
Enter fullscreen mode Exit fullscreen mode

Myth: asking the agent to paste the terminal is verification

This claim survives because it feels like an extra step, and extra steps get confused with controls. Pasting is still generation: the model can copy a prior block, invent a plausible duration, or drop the one failing test that would force a design discussion. Verification starts when a process you control writes bytes that the model cannot edit without going through the same wrapper. If the agent is allowed to rewrite artifacts/, the nonce is theatre, so keep that directory append-only in the session or copy it out immediately.

A small auditor makes the rule mechanical. It does not try to understand pytest; it only refuses the story when the story has no matching file. Treat the script as a proposed gate for humans and for any later CI job that should not trust chat.

#!/usr/bin/env python3
"""verify_pass_artifact.py — proposed check against narrated test runs."""
from __future__ import annotations

import argparse
import json
import re
from pathlib import Path

PASS_CLAIM = re.compile(
    r"(\d+)\s+passed",
    re.IGNORECASE,
)

def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--transcript", required=True)
    parser.add_argument("--artifacts", default="artifacts")
    parser.add_argument("--nonce", required=True)
    args = parser.parse_args()
    text = Path(args.transcript).read_text(encoding="utf-8")
    claimed = bool(PASS_CLAIM.search(text))
    artifact = Path(args.artifacts) / f"test-run-{args.nonce}.json"
    if not claimed:
        print("no_pass_claim_in_transcript")
        return 0
    if not artifact.is_file():
        print(f"FAIL claimed_pass_without_artifact nonce={args.nonce}")
        return 1
    payload = json.loads(artifact.read_text(encoding="utf-8"))
    if payload.get("nonce") != args.nonce:
        print("FAIL nonce_mismatch")
        return 1
    if int(payload.get("exit_code", 1)) != 0:
        print(f"FAIL artifact_exit_code={payload.get('exit_code')}")
        return 1
    print(f"OK artifact={artifact}")
    return 0

if __name__ == "__main__":
    raise SystemExit(main())
Enter fullscreen mode Exit fullscreen mode
python3 verify_pass_artifact.py \
  --transcript agent-session.md \
  --nonce "$AGENT_RUN_ID"
Enter fullscreen mode Exit fullscreen mode

Myth: a free remote workspace is already CI because both are somewhere else

Remote execution and continuous integration share a network location in casual speech, which is how the myth travels. CI is a contract about which command ran, against which commit, with which captured logs; a coding session is a conversation that can describe those things without performing them. Free model access and a free server option change the cost of trying the wrapper in a disposable workspace. They do not turn the workspace into a source of record until you keep the nonce files and the git sha together.

Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode's free model access and free server option are relevant when you want the wrapper, the tests, and the agent in one short-lived machine, then copy artifacts/test-run-*.json back to the canonical clone. The product does not certify that a transcript is true, and this article does not claim durability, hardware details, or a quota. The useful move is still local and dull: no artifact, no pass.

Myth: an exit code inside a fence is an exit code

Fenced shells look executable, especially when they include echo $? or a Process finished with exit code 0 line borrowed from an IDE. Those strings are not wait statuses from your kernel; they are characters. If you need an exit code, read it from the wrapper’s JSON, or from a CI system that you already trust for other jobs. A second proposed test keeps the rule in the repo so a later agent cannot “fix” a failure by editing the chat recap.

# test_wrapper_contract.py — proposed characterization test
import json
import os
import subprocess
import uuid
from pathlib import Path

def test_wrapper_writes_nonce_file(tmp_path, monkeypatch):
    root = Path(".").resolve()
    nonce = str(uuid.uuid4())
    env = os.environ.copy()
    env["AGENT_RUN_ID"] = nonce
    # Intentionally tiny: the contract is the file, not the product suite.
    result = subprocess.run(
        ["bash", str(root / "run_tests.sh"), "-k", "test_wrapper_writes_nonce_file"],
        cwd=root,
        env=env,
        check=False,
    )
    artifact = root / "artifacts" / f"test-run-{nonce}.json"
    assert artifact.is_file(), "wrapper must write a nonce-named artifact"
    payload = json.loads(artifact.read_text(encoding="utf-8"))
    assert payload["nonce"] == nonce
    assert "exit_code" in payload
Enter fullscreen mode Exit fullscreen mode

That test will look circular if you run it through the same wrapper it is describing, so keep a non-agent path in CI that invokes pytest directly on test_wrapper_contract.py. The point is not elegance. The point is that a future session cannot satisfy review by inventing a cleaner paragraph.

Myth: you can replay “the same command” from memory and call it confirmation

Re-running a command later is a different experiment unless you pin the commit, the nonce, the seed data, and the exact argv. Agents compress those details because transcripts are hostile to bookkeeping, and free sessions are often truncated when the conversation gets long. If confirmation matters, store the artifact and the git rev-parse HEAD in the same directory, then compare bytes, not recollections. The decision table below is the whole policy for most application repos.

Claim in the transcript Treat as Evidence before merge
“47 passed” in a fence Narration artifacts/test-run-<nonce>.json with exit_code 0
“I ran pytest” without a path Narration Wrapper argv recorded in the artifact
“CI would pass” Forecast The canonical pipeline on that sha
“exit code 0” as prose Fan fiction JSON written by run_tests.sh
“I pasted the terminal” Still narration File the model cannot mint unaided

Limitations and who should not use this

A nonce file proves that a wrapper ran and that it captured an exit code. It does not prove the tests were meaningful, that they touched the webhook path, or that the agent refrained from editing tests until they agreed with the sketch. Truncated transcripts will hide pass claims, which lowers false alarms and also hides the original lie, so keep raw logs outside the chat when the change is important. Teams with a mature CI requirement already have a stronger version of this control, and they should not replace it with a shell script living next to an agent.

Do not use this workflow as the only gate on payment, identity, or safety-critical code, because a JSON file cannot see a wrong trust boundary. People who cannot read failing tests should not let a free session become the test runner of record, even when the prose looks like a well-funded pipeline. If your last merge trusted a fenced pytest block, put the wrapper beside the repo and require the nonce file before the next argument about which model is better.

Top comments (0)