WRITING
A check that cannot fail tells you nothing
12 min read
A passing check is only evidence if it could have failed. A linter, a test, an audit query, a configuration setting, or a published policy that returns green or empty whether the system is healthy or the check is blind carries no information, because it would say the same thing in both worlds. Tests that always pass and audits that always come back clean are the most reassuring things in a codebase, and some of them are measuring nothing.
We have written about this pattern several times without stepping back to look at the instances together. Doing that turns up something none of the individual posts said. The checks did not go blind at random. Every one of them was blind in exactly the place where it shared an assumption with the bug.

Every blind check we found shared an assumption with the bug
The clearest case is a lint rule that kept passing because it had stopped checking anything. It banned a set of networking imports, and before matching it stripped string literals, so that a mention of a banned call inside a comment or fixture would not trigger it. That is standard practice. It is also why the rule could never see an import, because the module specifier in an import statement is itself a string literal. The text the pattern looked for was exactly the text the preprocessing removed. The rule ran on every commit and passed every time, and as that post put it, a rule that detects nothing produces the same output as a codebase with nothing to detect.
The next day it happened with a database audit. The query most people write to find functions that anyone can execute returns zero rows on a database full of world-callable functions. PostgreSQL records a function's default permissions as NULL, and NULL does not mean no grants. It means the defaults apply, and the default lets everyone execute it. The audit treated NULL the way a reader would, as nothing. The post names the mechanism precisely: this is the shape of bug that survives review indefinitely, because the verification step agrees with the mistake.
That sentence is the thread. The lint rule assumed string literals were noise, and so did the preprocessing that blinded it. The audit assumed NULL meant empty, and so did the query. In each case the check and the defect came from one understanding of the system, so the check was structurally unable to disagree with it.
Tests do the same thing in quieter ways. In one build we wrote up, a flow that transferred access was tested by asserting that it returned the right status and record. None of the tests attempted the access. The flow could not do the one thing it existed to do, and every test passed, because every test was checking the paperwork. In the same build, a sweep written to stop a class of bug checked that every reader called one specific shared function. It could not see a second derivation of the same kind, because it was written against the instance rather than the category. And a note in our own documentation claimed a test would fail until some user-facing copy was updated. The test asserted the absence of certain words, so it fired when someone rewrote the copy, not when someone forgot to. The note described what the test was for.
Configuration settings go blind through their names. Our analytics had an option to mask all inputs, which was on and doing its job: typed passwords showed as dots in session recordings. It did not mask text rendered on the page, and the admin page that displayed submitted inquiries was recorded in full. "All inputs" reads as everything a person typed, and the two stop being the same once what they typed is displayed back somewhere else. In the same audit, an option for IP handling turned out, on reading the installed code, to have no effect at all. Setting it would have produced a change that read as a fix, passed review, and did nothing.
Documents fail the same way, with nothing running at all. Our privacy policy said we did not use session recording, while session recording was enabled at the project level. It was a positive claim about a property of a system, written in good faith and never checked against the system. That observability post called it the same failure as a passing test that is not testing anything, applied to a document instead of a suite.
Put those side by side and the reason review does not catch them becomes obvious. A reviewer reading the lint rule, the audit query, the test, or the policy brings the same assumption the author had. That is what makes the assumption an assumption. Review adds a second pair of eyes looking from the same place.
Formal verification already had a name for this failure
Formal verification had given this failure a name long before any of our incidents: vacuity. A model checker can confirm that a property holds for a reason that has nothing to do with the behavior anyone cared about. The textbook example is a property stating that every request is eventually granted. In a system that never issues a request, the property passes, because an implication whose condition never occurs is trivially true. The check is green and the behavior it was meant to confirm was never exercised.
Beer, Ben-David, Eisner, and Rodeh formalized the detection of this in "Efficient detection of vacuity in ACTL formulas" at CAV in 1997, with a fuller treatment in Formal Methods in System Design in 2001. Their premise is ours: a successful verification result should itself be treated with suspicion until something shows it passed for the right reason. The motivation was practical. Experience with hardware verification had shown that trivially valid formulas were common, and that a passing result could hide a problem in the design, the specification, or the environment.
The problem has not gone away even where rigor is highest. A 2026 paper, "Verification Theatre", examines formally verified cryptographic libraries and sets out a taxonomy of the ways their verification boundaries give false assurance. If even formal verification can provide less assurance than its boundary suggests, ordinary application test suites deserve the same scrutiny.
Mutation testing turns the question into a routine
The practical question is how to find blind checks without waiting for an incident. Mutation testing answers it by breaking the code on purpose. A tool introduces a small fault and reruns the tests. If a test fails, the suite can see that kind of break. If every test still passes, you have found a blind spot before it cost anything.
Google has run this inside code review at scale. "State of Mutation Testing at Google", presented at ICSE in 2018, described surfacing a small number of mutants to developers during review, and reported that most of the surfaced mutants were judged useful. A later paper, "Practical Mutation Testing at Scale: A View from Google", described the same approach used by more than 24,000 developers on more than 1,000 projects. The detail worth copying is not the tooling. It is the inversion: instead of taking a green suite as evidence the code is correct, ask routinely whether the suite could go red, and show developers the answer where they are already looking.
That is what the controls in our lint rule and audit do by hand. Each banned pattern has a test asserting the rule fires on a violating snippet, and six of them went red the moment the import bans were added. The audit ships with a test that creates a function with default permissions and asserts the check fires on it. Mutation testing is the same idea applied without anyone having to remember to write the control.
Nothing ran is a different result from everything passed
Test runners already encode the distinction most checks lack. When pytest collects no tests at all, it exits with a distinct code: exit code 5, no tests were collected, rather than reporting success. Jest takes the same position from the other side: by default it fails when it finds no tests, and passing on an empty run is an explicit opt-in through a flag that allows the suite to pass when no files are found. Both designs treat an empty run as a question rather than an answer.
Most of the checks in the instances above had no equivalent. The lint rule reported that it had nothing to report. The audit returned zero rows. Neither said how much it had actually examined, so "nothing found" and "nothing looked at" produced identical output. That distinction is worth making loud everywhere, not only in test runners. Our own most recent post ended by admitting that our agent context file has never been measured with and without. That is a "not examined," and saying so plainly is the only thing that stops it from being read as "no findings."
The checking pipeline needs a check that is supposed to fire
Monitoring solved the last version of this problem with an alert that is meant to be firing at all times. The Watchdog alert in the Prometheus ecosystem exists to confirm that the whole alerting pipeline works: it always fires, and it should always reach a receiver. Its absence is the signal. If it goes quiet, the pipeline that carries every other alert has failed, and the silence of every other alert has become meaningless.
A quiet dashboard can mean a healthy system or a dashboard that stopped reporting. Only a check that is supposed to be loud can tell you which.
The method is independence, a demonstrated failure, and a stated scope
Across every instance, the fixes that worked had the same three parts, and they apply to checks that are not code.
The first is independence, and its purpose is specific. Where practical, evidence should come from a path that does not repeat the assumptions of the thing being checked. Independence is not valuable for its own sake. It is valuable because it breaks the shared assumption that blinded every check above. The audit was only fixed by asking the running database what was true, with defaults expanded, instead of reading the migrations, which described intent. The masking gap was only found by looking at what had actually been recorded. A check written from the same understanding as the code will tend to share its blind spots, which is why we keep arguing that the process that produced an artifact is the weakest source of evidence about it.
The second is a demonstrated failure. Before trusting that a check passes, make it fail against a case that is known to be bad. For a lint rule, that means a snippet containing the banned construct. For an audit, a deliberately misconfigured object. For a test of an access flow, a test that ends in an access attempt and asserts whether it succeeded, rather than a status field. For a published policy, the question is what state of the system would make the claim false, and whether anything would notice. A check that has never been made to fail has shown you that it runs. It has not yet shown you what it can detect.
The third is a stated scope. A check should say what it examined, including on the runs where it passes, and it should be written against the category of problem rather than the one instance someone thought of. The sweep that watched one shared function was complete for the instance it was written against and blind to the next one. It was fixed by making it enumerate the category, so that adding a new case is a row rather than a new check.
The principle stops where falsifiability gets expensive
Not every check can be made to fail cheaply, and pretending otherwise would be its own kind of theater. Some known-bad cases are expensive or dangerous to construct. Controls are code, and code has to be maintained. Mutation testing costs compute, and an always-firing alert is one more thing to run. There is a real budget for checking the checks, and spending all of it there is a failure too.
Controls can also go blind. When two screens in one build disagreed about the same record, the fix included a test that the two paths agree, plus a control asserting they also agree in the ordinary case, so the agreement test could not pass by both paths being broken the same way. The access-flow check we added is honest about its own limit: it confirms that an access was attempted, not that the attempt used what the transfer produced. The class of bug is narrowed, not closed. Writing that down is more useful than claiming it is solved.
And some failures that look like this are something else. Two correct tests that contradict each other are not blind. Each is doing real work, and each could fail. The problem is that neither compares its answer with the other's, which is a consistency problem rather than a vacuity problem, and it needs a different fix. Naming the pattern precisely matters as much as spotting it, because the wrong diagnosis sends you tightening checks that were never loose.
So the rule we actually follow is narrower than verifying everything and sharper than writing more tests. For each check that guards something you care about, know the last time it failed on purpose. If you cannot name that moment, you do not have evidence yet. You have a green result and an assumption, and the assumption may be the same one that produced the bug.
Written by Synthetixis, an AI-native product studio. More on what most AI software gets wrong.