A file that nobody wrote to, on a drive reporting perfect health, on a filesystem with no errors — quietly stopped matching itself. Here is how it was caught, what proved it, and why it stayed invisible for eighty-four days.
An enterprise storage node with two redundant server modules. One module came back from a routine restart and refused to start its application stack — seven consecutive failed starts, each dying on a class the runtime could not load. The other module, running the identical build, was fine and carried the workload throughout.
The two modules ship byte-identical copies of the same Java archive. Comparing them took one command.
Identical size, different content. That pairing is the whole finding, because it eliminates every ordinary explanation. A truncated write produces a shorter file. An interrupted copy produces a shorter or longer one. A bad deployment changes the modification time and usually the size. A partial overwrite changes the length.
None of that happened here. The file was written once, months earlier, and never touched again — same size, same timestamp, same everything the filesystem tracks. Yet the bytes on disk no longer matched the bytes that were written. The archive's own internal CRC, on a single compressed entry, is what finally exposed it: the entry was listed in the central directory at 8,547 bytes and read back as zero.
The data rotted in place.
Corruption is an easy thing to assert and a hard thing to prove, because almost every other explanation is more likely. Each of these was checked and ruled out before bit rot was named.
With the disk healthy, the filesystem clean, the file untouched and the build verified against a good twin, one explanation remains: the stored bits changed on media, silently, with nothing in the stack positioned to notice.
This is the part that should worry you more than the corruption itself.
The application had been running continuously for roughly eighty-four days. It loaded that archive once, at startup, and the operating system kept it in page cache from then on. Every subsequent read was served from memory — from a correct copy that had been read off disk before the rot occurred.
The corruption on the platter and the good copy in RAM coexisted for nearly three months. Monitoring was green. The application was healthy. Every health check passed, because every health check was exercising memory, not media.
The fault only surfaced on the first cold read. A clean shutdown flushed the cache; eleven minutes later the process tried to start, went to disk for the first time in three months, and got the rotted bytes. The restart did not cause the corruption — it revealed it.
Which raises the uncomfortable question: how many long-uptime systems are serving a cached copy of a file that no longer exists intact on disk? You would not know. Nothing in a normal monitoring stack looks.
A restart did not break it. A restart is simply when it stopped lying.
Here is why "same size, different hash" is a corruption fingerprint rather than a coincidence. Type anything below, then flip a single bit. The byte length will not change. The MD5 will change almost completely. That is the avalanche effect, and it is exactly what one rotted bit does to a file's checksum. Real MD5, computed live in your browser. Nothing is sent anywhere.
Run it a few times. The byte length never moves. The hash rarely keeps more than half its characters. That is the property that makes checksums useful for detecting corruption — and it is why a matching size with a mismatched hash is not ambiguous evidence. It is the signature.
Everything load-bearing in one place. Node identifiers, hostnames and organizational detail removed.
| Affected artifact | One Java archive in the application library directory |
| Size, both copies | 1,002,979 bytes — identical |
| MD5, failing module | 235c52f6…1cf6ede4 |
| MD5, healthy twin | a4278fd0…353e342d |
| Failed inner entry | 0 readable bytes, listed at 8,547 |
| Archive integrity test | FAIL — CRC |
| Blast radius | 1 corrupt file in the directory |
| Drive SMART | PASSED · 0 grown defects · 0 uncorrected |
| Filesystem | clean · r/w ordered · no errors · no read-only remount |
| Prior uptime | ~84 days · clean shutdown, first failed start 11 minutes later |
| Failed starts | 7 attempts · 0 successful |
| Verdict | Silent in-place corruption, exposed on first cold read |
| Outcome | Restored from verified twin · no outage · no data loss |
The repair took minutes: stage the twin's copy, verify it passes integrity before touching anything, back up the corrupt file for the case record, install atomically preserving ownership and mode, verify again. The module rejoined on the next supervisor cycle. Redundancy carried the service throughout, and no data was lost.
Not luck, exactly — but not design either. Two properties happened to be in place, and neither was there because someone planned for this.
This one was found because a restart forced a cold read on a file with an internal CRC and a good twin to compare against. Remove any one of those three conditions and the corruption stays invisible — or worse, gets read, used, and propagated as if it were correct.
Bit rot is not a folk tale told by storage people to justify budget. It is a low-probability event operating across an enormous number of stored bits for a very long time, which is a combination that makes rare things routine. The question is not whether it happens to your data. It is whether anything in your stack is positioned to tell you when it does.