False Absence: A Defect Class in Software That Reports on External State
Stephen Ukaegbu· Updated October 2026
Abstract
A false absence is a failed observation reported as a confirmed negative. This paper defines the class by four conditions, gives a test a developer can apply at the keyboard, and reports a catalogue of 32 call sites across 15 fix commits in a single production codebase. All 32 failed towards the reassuring answer, and 18 sat on code paths that already had tests, which caught none of them. The central result is uncomfortable: naming the class in a commit, documenting it in the affected functions and defending it with tests did not prevent the same author writing eight fresh instances in the same codebase within three weeks.
The class
A false absence is a failed observation reported as a confirmed negative. It requires four conditions, all of which must hold:
- A probe reads state the program does not control — a device, a filesystem, a registry hive, a remote service.
- The probe can fail without raising. It returns a value.
- At the point of use, that failure value is indistinguishable from a genuine negative: empty string, empty list, zero, false, missing key.
- The value is reported as a finding, unqualified, to a person or onto a record.
Condition 3 is the operational test, and a developer can apply it at the keyboard:
Does this probe's failure value equal its negative value?
If it does, any assertion built on it is unearned.
The direction is not incidental
Failure produces empty, zero, false, absent — and in a system that examines things for problems, those are exactly the values that mean no problem. A probe that fails resolves, every time, towards the reassuring answer.
The converse defect is self-correcting. A probe whose failure produced a spurious positive gets chased down within a day, because a false alarm costs somebody an hour and they complain. That is not hypothetical: in this same codebase, a misread binary registry value made machines appear domain-joined to an organisation that did not exist. It was caught almost immediately.
A false absence costs nothing visible. Nobody is interrupted by an answer that says everything is fine.
What was catalogued
32 call sites across 15 fix commits, over 82 days and 526 commits, in one production codebase written by one author. The inclusion criteria were written before the analysis.
| Finding | |
|---|---|
| Failed towards the reassuring answer | 32 / 32 |
| Reached a person, on screen or in a record | 31 / 32 |
| On a code path feeding a compliance output | 18 / 32 |
| In the component that inspects external state, rather than the server or web app | 28 / 32 |
The reported sentences were: no lock detected, no operating system installed, no TPM detected, not enrolled, not joined, no hidden drives, no encrypted volumes, no BIOS password, limitations: none reported, 0 ports responded, not present, movement and buttons work. Every one asserts that something which would have been a problem is absent.
The single instance that reached nobody is the one that destroyed data: an unreadable queue file treated as empty, and then rewritten. It produced no wrong sentence because it produced no sentence at all.
Twenty-three of the 32 are in two files, and 28 are in the component that reads hardware the program does not control. The class concentrates where code touches state it does not own, which is the sharpest practical guidance this catalogue offers.
Why the tests did not catch them
Eighteen of the 32 sat on code paths that already had tests. Those tests caught none of them. Examining each fixture gives two categories, which are the same blindness from opposite ends:
| Count | The fixture… | |
|---|---|---|
| Working probe supplied | 14 | …gave the probe something it could read |
| Adverse state never constructed | 4 | …never built the subject that matters |
Neither is negligence. A fixture is written from the author's mental model of the path, and that model is of the path working. Catching this class needs a fixture in which the probe fails and the subject is adverse — two independently unlikely conditions, jointly unrepresented.
During this work, four existing tests broke when a failure check was added. In every case the fixture was wrong, not the check: each stubbed a command that could not fail. One test's own name asserted the defect.
The recurrence finding
On 2 September a commit landed titled "Never let a failed probe read as a negative." It repaired six detectors, named the class in its own title, documented the reasoning in the affected functions, and defended the fix with 75 new lines of tests — a third of the diff. By any ordinary standard the lesson had been learned and recorded.
Nineteen days later, a systematic search of the same codebase found twenty-six further instances. That figure conflates two claims of unequal strength, so they are separated:
| Instances | What it means | |
|---|---|---|
| Fixed by the naming commit | 6 | The instances that prompted the naming |
| Introduced before it, found later | 18 | Naming failed to find these |
| Introduced after it | 8 | Naming failed to prevent these |
The eighteen are a failure of search, not of understanding — ten of them were introduced earlier the same day by the commit that created the detectors, which was verified by ancestry rather than assumed. Nobody claims a targeted fix is an audit.
The eight introduced afterwards are the finding. Every one was written seventeen to eighteen days after the class was named, by the same author, in the same codebase, with the naming commit in the history and its tests passing. Four of the eight are in the two files that commit had itself edited.
Three explanations, in increasing order of discomfort:
- The fix was applied to call sites, not to a shape. It introduced no type, no helper, no construct that would make the next probe honest by default. A lesson that lives in prose must be recalled; a lesson that lives in a type cannot be forgotten.
- The class is invisible at the moment of writing. The failure branch is not where the author's attention is, and the value it returns is the natural thing to return. Writing the defect requires no error and no carelessness.
- All eight were written in a two-day burst — the densest period in the project. They cluster precisely where new surface was being created fastest. The rule was not rejected under pressure; it never came to mind.
Stated precisely, and narrowly:
Naming a defect class, documenting it in the code, and defending it with tests was not sufficient to prevent the same author from writing eight fresh instances of it in the same codebase within three weeks.
If that is the outcome under conditions this favourable — one author, a small codebase, the lesson written in the file being edited — it is unlikely to be better on a larger team.
The response, and what cannot be claimed
Three things changed, none of them more documentation: a sweep that is run rather than hoped for; tests that assert the third value, not only the negative; and fixtures that can fail.
Whether this worked is not reported here. The observation window closes with the sweep. A codebase swept once is not a codebase that stays swept, and on the evidence above the honest expectation is that instances will accumulate again — which is precisely what the recurrence finding showed the first time.
Limitations
- One codebase, one author. The directional result is striking at 32/32, but it is a single project. Nothing here establishes a base rate for the class in general.
- The catalogue is a census of what was found, not of what exists. Instances still latent in the codebase cannot be counted, so every figure is a lower bound.
- The detection-method split is not a controlled comparison. The methods were applied at different times to different surfaces.
- Related work has not been surveyed. The class is adjacent to well-studied territory — error swallowing, exception handling, the semipredicate problem, null-versus-absent in type systems — and this paper does not yet position itself against that literature. No claim of novelty is made.
- The remedy is unevaluated. See above: the window closes with the sweep.