Make your coding agent prove its tests can fail
A green test suite can preserve the same mistake as the code. Ask an agent to show a bug its tests catch, then check where the expected answer came from.
An agent writes a patch, writes the tests for the patch, runs them, and reports success. The diff looks responsible. There's an implementation, a test file, and a green check.
The implementation and the tests may agree because they contain the same mistake. If the agent misunderstood a requirement while writing the code, reading that code to produce the expected answers can preserve the misunderstanding. A larger test suite can make the mistake harder to remove. Fix the implementation later and the tests complain.
I want a more useful completion report from a coding agent. Show the behavior the test protects. Show a plausible broken version that the test rejects. Then show where the expected answer came from. That last part matters as much as making the test fail.
Two branches, one missed bug
Take a deliberately small example. Shipping costs 500 cents for orders below 5,000 cents and nothing for orders at or above 5,000 cents. Order totals are nonnegative integers.
def shipping_fee(total_cents):
if total_cents >= 5000:
return 0
return 500
These tests execute both branches:
assert shipping_fee(4999) == 500
assert shipping_fee(5001) == 0
Now change >= to >. Both tests still pass. A customer with an order totaling exactly 5,000 cents gets charged for shipping, in violation of the policy. The tests reached the code. They didn't distinguish the two rules.
Adding this assertion catches the difference:
assert shipping_fee(5000) == 0
The test passes on the correct implementation and fails on the altered one. We can point to the requirement and explain why the failure matters.
The runnable example also tries two other faults, always charging for shipping and never charging for shipping. The original examples reject two of the three chosen faults. Adding the boundary case rejects all three. An equivalent rewrite still passes.
This is a constructed example, not an account of a model session. Three caught faults are three caught faults. They tell us nothing about a checkout bug outside those cases.
We can now name a specific mistake these tests would stop. That gives a reviewer something to inspect beyond a successful test run.
Reading the code can contaminate the answer
There is direct research on this problem. In a July 2026 preprint, Michael Konstantinou, Florian Tambon, and Mike Papadakis compared test-generation workflows across five models and three Python programming benchmarks.
They generated implementations, kept executable but incorrect ones, filtered out easy faults, and selected a difficult faulty implementation per task. Tests generated in a conversation containing the faulty implementation detected about 14% of faults. Tests generated in a fresh context from the task description detected about 25%. Detection required rejecting the faulty implementation while accepting the reference solution.
Those percentages describe selected faulty benchmark programs. They are not estimates of how often a production coding agent breaks your application. The result does support a practical concern, though. Showing a test generator the implementation can make its expected answers follow the implementation's mistake.
Huang and colleagues found that correct implementation context helped test generation compared with incorrect code. Reading code can supply useful information. The problem is treating its behavior as the answer key before establishing that the behavior is right.
A fresh context helps separate the work. It doesn't make an ambiguous requirement precise, and a second model can misunderstand the same requirement. The useful question is where the expected behavior came from, not how many agents agreed with it.
Testing the tests is old work
Changing the comparison operator is a basic example of mutation testing. Deliberately alter the program, run the tests, and see whether they detect the change. A detected mutant is conventionally called killed. A surviving mutant is a reason to investigate the tests, the change, or both.
The idea predates coding agents by decades. Richard DeMillo, Richard Lipton, and Frederick Sayward described the approach in their 1978 paper on test-data selection. Coding agents give us another reason to use it.
Meta already combines it with language models. Meta's ACH system starts with an engineering concern, generates simulated faults that existing tests miss, and asks a model for tests that pass on the original code and fail on the altered code. It filters candidate changes and sends the resulting tests through review.
In Meta's Android Kotlin evaluation, 277 of 571 mutant-killing tests, about 49%, added no line coverage. They found distinctions inside code the tests already reached. A filter demanding new line coverage would have discarded those tests.
ACH hardens existing behavior against selected simulated regressions. It doesn't establish that the existing behavior satisfies every requirement. Still, this is a concrete use for generated tests. A reviewer can inspect the fault each proposed test catches.
Attach that example to the agent's work. I can review the rule, the fault, and the assertion together. A list of new test names tells me much less.
Sometimes preserving current behavior is exactly the assignment. A regression suite can protect a working application during a refactor, even when nobody has a complete written specification. Meta's TestGen-LLM explicitly filters for tests that build, pass repeatedly, and add coverage. That is useful hardening. The mistake is treating preserved behavior as evidence that a new business rule was implemented correctly.
The answer key can be wrong
Return to the shipping example and change the boundary assertion:
assert shipping_fee(5000) == 500
Now the correct implementation fails and the broken implementation passes. The test is specific. It executes the relevant branch. It also demands the wrong answer.
An agent trying to get a green run could change the code to satisfy it. An agent starting with the broken code could generate it by copying what the code already does. In either case, the green check would endorse the shipping bug. When a requirement-backed test fails, investigate it before changing the expected answer. Otherwise the agent can complete the task by deleting the evidence against its patch.
This is the test oracle problem. An oracle decides whether an observed result is correct. Staats, Whalen, and Heimdahl's work on testing foundations treats the program, test set, and oracle together. Test quality depends on their relationship. Inputs that reach interesting code are insufficient when the decision about correctness is wrong.
Mutation testing doesn't supply a missing business rule. You can preserve the wrong boundary and generate plenty of other alterations that your tests reject. The score rises while the customer still gets charged incorrectly.
For this example, the source of the expected answer is explicit. The policy includes the threshold. In an application, the source might be an agreed requirement, a protocol rule, a previously confirmed bug, or an independently checked calculation. Each assertion should have a reason beyond the output the current code happened to produce.
If nobody can say whether shipping should be free at exactly 5,000 cents, settle that question. Generating an assertion doesn't settle it for you.
Failures need interpretation too. Meta's 2026 work on just-in-time catching tests separates a test that detects a change from one that detects an unintended change. Engineers confirmed eight true positives among 41 candidate catches reported to them. A failing generated test can be useful, wrong, or objecting to the change you actually requested.
Make the extra work earn its cost
Running a test suite against every conceivable alteration is expensive. Reviewing useless failures is expensive too. An agent can produce either kind of bill without much encouragement.
Google's 2018 mutation-testing report describes a more selective approach. It focuses on changed code, suppresses unproductive mutation targets, and limits how many mutants it generates. Findings appear in code review, where someone can act on them. The authors treat developer attention as a cost alongside execution time.
For an agent making a bounded change, start with the behavior the change could damage. A shipping change suggests boundary faults. An authorization change suggests an incorrectly permitted operation. Choose the failure before congratulating the agent on the number of tests it wrote.
Then ask it to produce a small, inspectable result:
- The requirement supporting the expected behavior.
- A test that passes on the proposed implementation.
- A plausible behavioral fault that makes that test fail at the relevant assertion.
- The actual execution results, including faults that survived and checks it could not complete.
Keep the deliberately broken variants in disposable copies. A syntax error or missing import doesn't demonstrate that an assertion catches the business-rule violation. Run the same test against the original and the altered version, with the expected answer held fixed.
Also check that the test tolerates a valid rewrite. Equivalent mutants change the code without changing the relevant behavior. Demanding failure on those changes encourages tests that police implementation details. Don't turn a mutation-score target into an excuse to make refactoring miserable.
These checks still cover only the cases and faults you chose. They don't replace integration tests, checks against known real bugs, or review of a requirement nobody has managed to express correctly. Spend that effort on the behavior whose failure would matter.
The agent can help construct all of this. It can suggest a fault, write a test, execute both versions, and show the difference to a reviewer. What it cannot do is turn agreement with its own implementation into an independent reason to believe the implementation.
When an agent says the tests pass, I want it to show the bug those tests would have stopped.