An Empty Result and a Failed Read Look Identical
Here is a file move that verified itself before deleting anything. Hash the source. Copy. Hash the destination. Compare the two. Delete the source only if they agree.
source folder -> {} read failed, error swallowed
destination folder -> {} nothing copied, so nothing to read
{} == {} -> match, move verified
-> DELETE SOURCE
The verification passed because the read had failed.
An unreadable folder returned an empty set. So did the destination it was never copied into. Two empty sets are equal, the guard was satisfied, and the delete was authorised. Nothing crashed, nothing warned, and the log line said verified.
It was caught by auditing every entry marked verified for a file count of zero. There were none, so nothing was destroyed. Luck decided that, not design.
Why the wrong answer is the safe-looking one
A crash gets investigated. An empty list gets used.
That asymmetry is the entire problem. When a read fails loudly, someone looks at it, and the cost is ten minutes. When a read fails into a default, it returns the one value that every piece of downstream code is willing to accept without question. The failure mode that produces no symptom is the one that travels furthest.
Four things follow from it, and they compound.
It licenses destructive action. Any code that reasons source and destination now hold the same thing will act on that conclusion. Delete, overwrite, close, archive, mark complete. An empty answer satisfies every comparison of that shape.
It produces artefacts that assert the absence. A manifest, an inventory, a seal, a count. Those outlive the moment they were made and are read later as ground truth by someone who was not there when the read failed. The artefact does not record that it could not look. It records that there was nothing.
Two components agreeing does not make it true. If the thing that writes and the thing that checks both use the same forgiving read, they will agree on the same incomplete answer and the check will pass. Agreement between two blind processes is not corroboration.
It survives code review, because there is no error path to inspect. A reviewer looks for the handling of a failure and finds none, which reads as this cannot fail rather than this failure is invisible.
The same pattern, five times in one day
This came out of migrating a corpus of matter files into an encrypted vault. Once we knew the shape, it was everywhere.
The move script above was the first. Then a shell command reported zero files for folders it had no permission to enumerate, because the flag that suppresses the error message also suppresses the fact that there was one. Those zeroes were passed to the operator as counts.
An inventory tool was pointed at a folder whose contents had already been moved. It found a single leftover file and wrote a manifest recording that the corpus contained one matter. It did not crash and it did not warn. The manifest was accurate about what it saw and completely wrong about what existed.
The one that mattered most never fired. Our delivery packer built its seal by walking the recipient folders using the same permissive default. An unreadable folder would have been sealed over: a manifest covering less than the pack, a verification step walking the same way and reaching the same total, the two agreeing, and the pack passing verification while missing evidence. A seal that verifies while incomplete is worse than no seal, because it converts a gap into an attestation.
Then the one that had nothing to do with our code at all. The sync client reported everything backed up and synced while holding 153 of 264 files as cloud placeholders, with the setting that forces local copies confirmed as switched on. A vendor's status is a claim about the vendor's queue. It is not a claim about what is on your disk.
Where this sits next to a nil result
We have written before that a nil result is not a finding until you prove you could have found something. This is the same failure arriving from the other direction.
There, the question was whether an absence had been established well enough to count as evidence. Here the question is earlier and cruder: whether the zero in front of you is an absence at all. A nil result needs a control before it becomes evidence. A zero needs a control before it becomes a nil result.
Both are versions of the habit that runs through most of this work. A description of a thing is being trusted in place of the thing, which is also what goes wrong when cataloguing an exhibit stands in for reading it. The same zero reaches you from people as well as from code: on an intake form, a blank field is not a no, and it is read as one.
What to do instead
Make the read strict wherever its answer authorises an action. The permissive default is fine for a listing you are going to look at yourself. It is not fine for a read whose result decides a delete, a seal, a manifest or a count. Those calls should raise rather than shrug.
Never let "nothing found" and "could not look" share a return value. This is the structural fix and the only one that holds. Return a state alongside the data. Our inventory tool now answers with one of four named conditions rather than a bare collection, and it refuses to write a manifest at all in the states where it cannot see the corpus. Refusing to produce the artefact is the correct behaviour, and it is exactly what the empty-collection default takes away from you.
Do not let the checker inherit the writer's blindness. If a seal is built by walking a tree, the verifier must not walk it the same way. Two independent methods that disagree tell you something. Two identical methods that agree tell you nothing you did not already assume.
Audit for the pattern, not the instance. Every one of these was found by searching for the shape rather than waiting for a symptom: enumerations with no error handler, suppressed error flags, exception handlers that pass silently. Each hit is a place where an empty answer might be a lie. Most will be harmless. You are looking for the ones sitting upstream of a decision.
What to ask, if you are the one commissioning the work
- "What would your tool have returned if it could not read one of the folders?" If the answer is the same thing it returns when a folder is genuinely empty, the tooling cannot tell you the difference, and neither can the person operating it.
- "How many files does the seal cover, and where did that number come from?" A count read off the finished artefact is evidence. A count produced by the same process that built it is a restatement.
- "Does anything in your process refuse to produce a document?" A system that always emits a manifest will emit one when it is blind. The refusal is the control.
- "What are you treating as proof that the files are actually there?" If the answer is a sync client, a dashboard or a green tick, that is a vendor's claim about its own queue.
An empty result is not a result. It is a shape that a result and a failure both fit into, and the only thing separating them is whether somebody built the machinery to tell them apart. A green tick also expires: a check that passed last week says nothing about the files that go out today, which is passed at build time is not passed at send time.
AI9OS turns public information into verified, chain-of-custody findings for licensed investigation agencies, law firms and corporate risk teams.
Request a demo