A green test suite on an AI-generated change tells you the code does what its tests check for. It does not tell you the tests check for the right thing, and when the same process wrote both the implementation and the tests around it, those two facts can drift apart without a single test failing. This is a documented, repeatable pattern in AI-generated code specifically, not a general testing problem: tests written by reading an implementation tend to assert what the implementation does, not what it was supposed to do, and silent logic failures on edge cases are described as accounting for a majority of the faults found in this category. Catching it requires a different kind of test than the ones an assistant tends to generate alongside its own code, plus actually running the feature by hand before trusting the build.
Why a green test suite does not mean correct behavior
Tests exist to verify that code matches a requirement. That verification only works if the test was written from the requirement, independently of the implementation it checks. When an assistant is asked to write both the code and the tests for it in the same pass, or is asked to “add tests for this” after generating the implementation, the tests it produces are derived by reading what the code already does, not by reasoning back to what was actually asked for. If the implementation silently does the wrong thing in a specific case, a test generated from that same implementation confirms the wrong behavior instead of catching it, because from the model’s position at that moment, the implementation is the source of truth about what “correct” looks like.
The tautological test problem
This is worth naming precisely, because it explains why the failure survives a passing test suite rather than being caught by one. A test is tautological in this sense when it restates the implementation’s actual behavior as the expected behavior, rather than deriving the expected behavior independently from the original requirement. If the underlying code silently drops a modifier under a specific condition, an assistant reading that code and generating a test for it will tend to produce a test confirming the modifier gets dropped under that condition, because that is what the code in front of it does. The test passes. The build is green. The bug ships anyway, protected rather than caught by the very test meant to catch it.
Where this bites hardest: edge cases and implicit conventions
The failure concentrates in specific places rather than spreading evenly across a codebase, which is also what makes it findable once you know where to look. AI-generated code tends to handle the common, well-represented case correctly and cleanly, because that is the pattern most heavily represented in what the model has seen. It tends to handle ambiguous formats, boundary conditions, and conventions that are implicit in a specific codebase rather than stated anywhere, less reliably, because those are exactly the details a model has no way to infer from the surrounding code alone. A date-parsing routine that handles common formats correctly but silently applies the wrong regional convention to an ambiguous date is the shape of bug this pattern produces: correct-looking on the input you’d naturally try first, wrong on the input that actually matters.
What actually catches it
Favor tests that check outcomes over tests that check implementation details. A behavioral or integration test that runs the actual flow and checks what came out the other end is harder to write in a way that merely restates the implementation, because it is anchored to an observable result rather than to the code’s internal steps. A narrow unit test written by the same process that wrote the function it tests is the weakest form of evidence in this specific failure mode, not because unit tests are bad in general, but because this particular failure mode is exactly the one they are least equipped to catch.
Write or review at least the critical-path tests independently of the implementation, from the original requirement rather than from reading the code afterward. This is more work than accepting a generated test suite wholesale, and it is specifically the work that closes the gap this failure mode depends on.
Run the feature yourself before trusting the green build. This is worth repeating from the broader merge checklist because it is the single check that catches what an automated test, of any kind, structurally cannot: a genuinely novel case nobody wrote a test for at all. A passing test suite is necessary. It has never been sufficient, and it is specifically less sufficient than usual on AI-generated code where the tests and the implementation share a common author.
How this connects to the rest of the review discipline
This failure mode sits next to, but is distinct from, checking that a called function or API genuinely exists: that check catches code calling something that is not real at all, while this one catches code that calls something real and still produces the wrong result in a case the tests never exercised. Both are review disciplines specific to AI-generated code rather than general software-quality advice, and insufficient or drifted codebase context is one of the root causes that makes this failure more likely in the first place: a model that never saw the convention it needed cannot be expected to apply it correctly, tests included.
FAQ
Does this mean I should not trust AI-generated tests at all?
Not entirely, but treat them as a starting point rather than final verification, particularly for anything on a critical path. They are useful for catching obvious regressions and are weakest exactly where independent verification of the original requirement matters most.
Is this specific to any one AI coding assistant?
No. The pattern comes from how a single process generating both an implementation and its tests tends to reason, which applies across current AI coding assistants rather than to one product’s specific behavior.
How can I tell if a bug like this has already shipped?
There is rarely a clean signal from the test suite itself, since by definition the tests are passing. A production incident or an edge case a user hits that “shouldn’t be possible” given the passing tests is the more common way this surfaces after the fact.
Should I write tests before asking the assistant to generate the implementation instead?
Writing the test from the requirement first, independent of any generated implementation, is a stronger practice for exactly this reason: it forces the expected behavior to be defined before the code that might quietly redefine it exists.
Is this worse for large changes than small ones?
It scales with how much of the reasoning the assistant did unsupervised, not strictly with size. A small function handling an ambiguous edge case can hide this failure just as effectively as a large one, if nobody independently checked what the correct behavior for that edge case actually was.
