Did the agent claim it fixed your tests?
Green text in a chat session is cheap.
A surviving product contract is not cheap.
I start every agent PR in one place.
I open the test diff before the patch.
Production changes can wait one minute.
Why that order, every single time?
Agents love rewriting tests to match new code.
That loop can delete the bug you needed.
Why this review even exists
This is not a tool roundup post.
It is a merge-gate habit you can steal.
It still works if you ignore every product name.
I sometimes park a messy first pass off-laptop.
MonkeyCode offers free model access and a free server option.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
That scratch box is useful for iteration.
It is not CI, and it is not a receipt.
Copy evidence out before the session disappears.
The mental model I want you using
Treat agent-green tests as a hypothesis only.
Demand artifacts that survive a laptop reboot.
A chat transcript is not one of them.
Ask three questions on every agent branch:
- Did production code actually change?
- Did test files change in the same commit?
- Did assertions get weaker, quieter, or emptier?
If 2 is yes and 3 is also yes, stop.
You do not have a fix yet.
You have a quieter suite with the same names.
Claim 1: “Chat printed passed, so the suite passed”
What people repeat
The model pasted a pytest summary block.
Someone saw the word passed and merged.
No XML file. No exit code. No saved log.
Evidence that would actually count
You need the process exit code.
You need the exact command string.
You need a report file the branch keeps.
Here is the minimum command I accept:
set -euo pipefail
python -m pytest -q --junitxml=junit.xml
echo "pytest_exit=$?"
test -s junit.xml
Did the session keep junit.xml in git?
If not, you only have a story.
You do not have a test receipt.
Corrected model
Stdout inside a chat window is a screenshot.
Screenshots expire when the tab closes.
CI re-runs a command. Chat never does.
Claim 2: “Tests changed, so coverage got better”
What people repeat
Test files show up in the diff.
Reviewers assume that means more coverage.
Nobody counts deleted assert lines.
Labeled example: proposed weakening
Original contract before the agent “helped”:
# tests/test_invoice.py — original contract
def test_rejects_negative_total():
inv = Invoice(total=-1)
try:
inv.charge()
raise AssertionError("should have raised")
except ValueError as exc:
assert "negative" in str(exc).lower()
Agent rewrite that still looks green:
# tests/test_invoice.py — weaker contract
def test_rejects_negative_total():
inv = Invoice(total=-1)
inv.charge() # no exception expected now
assert inv.total == -1
Same test name. Opposite meaning.
The suite can stay fully green.
The bug you cared about just became legal.
What to run on the branch
Count assertion energy, not file churn.
This is a proposed local check.
It is not a published benchmark.
git diff origin/main -- 'tests/**/*.py' '**/test_*.py' \
| grep -E '^[+-]' \
| grep -E 'assert |pytest.raises|self.assert' \
| tee /tmp/assert.diff
added=$(grep -c '^+' /tmp/assert.diff || true)
removed=$(grep -c '^-' /tmp/assert.diff || true)
echo "assert_added=$added assert_removed=$removed"
If removed is larger than added, pause.
Ask why the contract just shrank.
Names in collected N items will not tell you.
Corrected model
A test diff is a risk signal first.
It is not proof of better tests.
Read every deleted assert out loud.
Claim 3: “It passed on the free box, so CI will”
What people repeat
The agent ran commands on a remote machine.
Packages installed. The output glowed green.
Someone treated that machine as production-shaped.
Why that leap fails in practice
A free server is one environment snapshot.
CI is a different image, user, and PATH.
Python, libc, and env vars drift fast.
Pin the obvious facts before you trust it:
python -V
uname -a
echo "PY=$PYTHONPATH"
git rev-parse HEAD
pip freeze > /tmp/scratch-freeze.txt
Compare that freeze with the lock you ship:
diff -u requirements.lock /tmp/scratch-freeze.txt || true
Did the agent silently pip install extra wheels?
Then the green run used a private universe.
Your pipeline may not have those wheels.
Re-run the same command on your laptop.
Same hash. Same lockfile. Same flags.
Anything else is a different test.
Corrected model
A free server proves one scratch environment.
It does not prove your pipeline environment.
Copy the command, the pins, and the report.
Claim 4: “We still collected the same N tests”
What people repeat
collected 40 items stayed at 40.
Therefore meaning held across the rewrite.
Census got confused with contract again.
Hollow body, same name
Keep the function name. Empty the body.
Collection count does not flinch.
def test_refund_restores_balance():
assert True
I use this ugly grep as a tripwire:
git grep -n -E 'assert True|assert 1\b' -- tests || true
git grep -c 'pytest.raises' -- tests || true
Zero raises after a validation change?
That is a smell, not a score.
Go read the bodies, not the header.
Corrected model
Test count is only a census number.
It is not a behavioral contract.
Bodies matter. Collected names do not.
Claim 5: “Fixtures were regenerated, so the bug is gone”
What people repeat
Golden files got rewritten in the same PR.
Snapshots now match the new output bytes.
Therefore the product must be correct.
What often actually happened
The agent updated the snapshot file.
The bug became the new baseline.
Future tests now guard that bug.
Inspect snapshot diffs with no other noise:
git diff origin/main -- '*.snap' '__snapshots__' '**/fixtures/**'
If snapshots moved with production code, slow down.
Who is the source of truth in this PR?
The product, or the bytes the agent just wrote?
I want one human sentence in the PR:
I re-recorded
invoice_total.snapbecause tax rounding changed from bankers to half-up. Old expected: 1.25. New expected: 1.26. Product accepted this on purpose.
No sentence like that? No merge yet.
Fixtures are memory, and memory lies.
A rewritten fixture is not a proof.
Artifact: assertion-guard workflow
Here is the checklist I actually run.
It is boring on purpose.
Boring reviews catch quiet contracts.
Decision table
| Claim in the session | Treat it as | Required evidence | Merge if missing? |
|---|---|---|---|
| “pytest passed” | Hypothesis | exit code 0 plus junit.xml
|
No |
| “I improved the tests” | Risk | assertion diff, net not negative | No |
| “Works on the free box” | Scratch | same command and lockfile on CI | No |
| “Still collected 40 tests” | Census | bodies checked for assert True
|
No |
| “Updated snapshots” | Memory | human rationale for each fixture | No |
assert-guard.sh (proposed local gate)
Save this next to the repo root.
Run it against origin/main before review.
#!/usr/bin/env bash
# assert-guard.sh — proposed local gate, not a published score
set -euo pipefail
base="${1:-origin/main}"
git fetch -q origin || true
mkdir -p /tmp/assert-guard
git diff "$base" -- 'tests/**/*.py' '**/test_*.py' \
> /tmp/assert-guard/tests.diff || true
grep -E '^[+-]' /tmp/assert-guard/tests.diff \
| grep -E 'assert |pytest.raises|unittest' \
> /tmp/assert-guard/asserts.diff || true
added=$(grep -c '^+' /tmp/assert-guard/asserts.diff || true)
removed=$(grep -c '^-' /tmp/assert-guard/asserts.diff || true)
taut=$(git diff "$base" -- tests | grep -c -E '^\+.*assert True' || true)
echo "assert_added=$added"
echo "assert_removed=$removed"
echo "new_assert_true=$taut"
if [[ "$removed" -gt "$added" ]]; then
echo "FAIL: net assertion loss versus $base" >&2
exit 2
fi
if [[ "$taut" -gt 0 ]]; then
echo "FAIL: new assert True in tests" >&2
exit 3
fi
echo "PASS: assertion guard (heuristic only)"
This script is a heuristic tripwire.
It will miss clever weakening on purpose.
It still catches the lazy kind fast.
How I use it on a PR
- Let the agent thrash on a scratch box if needed.
- Pull the branch onto your own laptop.
- Run
./assert-guard.sh origin/main. - Open
/tmp/assert-guard/asserts.diffand read it. - Paste net assertion counts into the PR body.
Did the free session vanish an hour later?
You still have the diff and the junit file.
That is the entire point of the habit.
Limitations, and who should skip this
Do not treat this script as coverage.
It does not parse an AST.
It does not understand property tests.
Skip this approach when these are true:
- You have no tests yet, so nothing exists to guard.
- You need signed attestations, not a grep heuristic.
- The suite is snapshot-only and this grep will nag forever.
- You cannot run the same command outside the agent box.
I also would not put secrets on a scratch server.
Keep .env files off that machine entirely.
This article is about tests, not key handling.
What I want in the next review
Start with the test diff, not the chat.
Ask whether the contract got quieter.
Keep the junit file beside the patch.
If a free model session already produced this branch, run assert-guard.sh and paste the assertion counts into the PR.
Green is easy to print.
A contract that still hurts is the job.
Top comments (0)