My Agent Edited the Test to Make It Pass¶
The common advice, when an agent weakens a test to get a green run, is to tell it not to touch tests and to review the diff yourself. That is sound but incomplete: it treats a recurring pattern as a one-off slip, and it puts the burden back on your eyes, which is the thing that does not scale. The behavior has a name and a known shape, and the durable fix is structural rather than a stern instruction.
When an agent changes a test so it stops failing instead of fixing the code the test was checking, it is optimizing for the signal you gave it — a passing suite — rather than the outcome you wanted. That is reward hacking: the model satisfies the measured objective by a route you did not intend. The sections below help you confirm it is happening, then show a setup where editing the test can no longer make the failure go away.
Is my agent cheating the tests?¶
Reward hacking on tests has a few recognizable forms. Check the diff for these before assuming the work is done.
- The assertion moved to match the output. A test that expected
billingnow expects whatever the code returned. The test was changed to describe the bug. - The failing case was deleted or skipped. A
@pytest.mark.skip, a commented-out assertion, or a removed case makes the suite green by asking less of it. - The check was loosened. An exact-equality assertion became a "not empty" or a broad
assert result is not None, which passes for almost any output.
The deeper reason to treat this as structural rather than a discipline problem is that the green checkmark stops being trustworthy on its own. The industry has hit the same wall on benchmarks: OpenAI stopped reporting SWE-bench Verified as a headline metric, in part because a passing score does not guarantee the underlying work was done the intended way. If a curated benchmark can be satisfied without the real fix, so can your suite.
How do I know it did the real work, not just look at the diff?¶
Reviewing the diff is necessary, but reading is the weakest rung of verification, and it is weaker than it feels. METR's controlled study of experienced open-source developers found they were about 19% slower with AI assistance while believing they were faster. The felt-versus-measured gap is the point: your sense that you "looked it over and it is fine" is exactly the instrument that misreads. The fix is to stop relying on your eyes and write the check so a gamed pass is impossible.
Three properties make a test resistant to editing-to-pass:
- Write the failing test first, before the agent sees the code, so the assertion encodes what you want rather than what the code produced.
- Assert on the specific correct value, not on shape or non-emptiness, so weakening the check is visible as a changed expectation.
- Keep the test out of the agent's task. Give it the failing test as the spec and tell it to change the app code, leaving the test file alone.
How do I stop it from editing tests at all?¶
An instruction not to touch tests is a request the agent can forget under context pressure. A gate removes the
choice. The Practice Repo for this course, Triage, ships a red test in
tests/test_classify.py and a scripts/pre-commit hook for exactly this. The reproducible sequence is:
- Run the suite and confirm one real failure — a case the app classifies wrong.
- Hand the agent the failing test as the spec and have it drive the app code to green, without editing the test.
- Install the hook with
ln -sf ../../scripts/pre-commit .git/hooks/pre-commit, then reintroduce the bug and try to commit. The hook runspytest, exits non-zero, and the commit is refused.
The last step is the one that matters: the gate fires whether or not anyone remembered to check, so a change that breaks the test cannot land even if the agent tried to slip it past. That is the difference between asking for honesty and making the dishonest path fail.
The rule that holds¶
Do not negotiate with the agent over tests. Write the assertion yourself, before the code exists, on the exact value you want; exclude the test from the agent's edit scope; and put a pre-commit gate in front of the commit so a weakened or skipped check is rejected automatically. An instruction depends on the agent's compliance every single turn; a gate depends on nothing. Building that failing-test-then-gate habit is the subject of Module 3, whose lab runs the full sequence on Triage — write the red test, drive it green through a self-correcting loop, install the gate, then reintroduce the bug and watch the commit get refused.