DEV Community

Cover image for Debugging at 3 AM: A Horror Story With No Villain Except Me
v. Splicer
v. Splicer

Posted on Originally published at Medium

Debugging at 3 AM: A Horror Story With No Villain Except Me

Production was down, the dashboard was bleeding red, and every suspect had an alibi. The killer had my git credentials.
3:04 A.M.

The phone glows at 3:04 in the morning, and there is a grammar to that light. Not the white of a text from someone who loves you. Not the green of a package arriving. The amber of the paging app — the color of weather emergencies and furnace warnings; the color machines use when they want to inform you, courteously, that something you built is misbehaving in public.

I sat up in the dark and opened the laptop, which lit my face the way open freezers do in films about regret.

The dashboard was a Jackson Pollock painted entirely in red. Our authentication service — the front door of the product, the one service contractually obligated to be boring — was failing at a rate I had previously considered theoretical. Users weren't just failing to log in. They were being logged out. Mid-session, mid-sentence, all across the map, in every timezone our little empire covers.

Every engineer has a private horror story, and we tell them the way fishermen describe weather: exaggerated, semi-mythological, absolutely load-bearing. I want to tell you mine as what it actually was — a horror story — because it has every required element. A remote cabin (my apartment). A storm (rain, theatrical, unpaid). An isolated protagonist (me, insufferable). And a monster no search party could ever find, because I was about to spend four hours learning that the monster had my face and my commit history.

What it doesn't have is a villain. That's the part the genre never prepares you for.

The Fine Art of Needing Someone to Blame

Every incident has a casting call, and mine convened immediately.

Suspect one: the deploy. It is always the deploy; this is the closest thing software has to folk wisdom, and folk wisdom survives because it is usually right. I checked. Nothing had shipped in four days. The most recent release had the alibi of sediment.

Suspect two: the network. "It's not DNS," I told the empty room. "There is no way it's DNS." I checked the DNS anyway, the way you check behind the shower curtain in a horror film — not because you expect to find anything, but because the genre demands one token act of courage. The DNS was fine. Somehow its innocence felt like a betrayal.

Suspect three arrived on schedule, and I am not proud of this: the new engineer. She joined three weeks ago. She had shipped nothing, touched nothing, and was, at that moment, asleep like a person with a clear conscience — statistically the most suspicious condition there is. By 3:40 I had mentally drafted a post-mortem with her name in it. Gently worded. Professionally devastating.

Here is what I was actually doing, though I lacked the vocabulary for it at the time: running a comfort ritual. Humans are blame-seeking missiles. Hand us an unexplained catastrophe and we will generate a villain with the reflexive urgency of an immune response. Villains make chaos narratable. A system that failed for seventeen interacting reasons is a nightmare you can't repeat at parties; a system that failed because Dana pushed on Friday is a story, and stories feel like control.

The safety literature worked this out decades ago, in an industry that counts its dead. James Reason's Swiss cheese model — the one from aviation, the one that says disasters happen when the holes in several layers of defense happen to line up — exists precisely because "one careless person" is almost never the truth of a serious failure. Charles Perrow went further in Normal Accidents and argued that in systems complex enough, breakdowns are not anomalies at all. They're residents. They pay rent.

I knew all of this. I had once cited it at an all-hands, in a confident voice, with a slide. At 3:40 A.M., knowledge and comfort are different organs, and only one of them was working.

The Bug With a Bedtime

Salvation, when it came, came from the most suspicious direction available: arithmetic.

Coffee number three had arrived not as a beverage but as a coping mechanism with a handle. I stopped staring at the aggregate error graph — staring at aggregates at 4 A.M. is just doom with a scrollbar — and sliced the errors by hour instead.

And the error rate had a bedtime.

3:00 A.M. Sharp. A spike, a twenty-minute decay, silence. I scrolled back one day: 3:00 A.M., spike, decay, silence. Two days: the same. Seven nights, the same. A metronome wearing a ski mask. Whatever this was, it wasn't random and it wasn't ambient. It was scheduled. Something in my system looked at its watch every night, said now, and did a murder.

Naturally, it refused to happen in staging. I pointed every instrument I owned at the staging environment and it sat there, serene, error-free, smug in the way only a system without real traffic can be. For one feverish hour this felt like a genuine heisenbug — a bug that flees observation, a witness who heard a noise and declines to repeat it. Purists will object that an environment difference is not a true heisenbug, and the purists are right, but at 4:20 A.M. every bug that hides from daylight qualifies as uncanny.

I explained all of this, in detail, to the rubber duck on my desk. His name is Vladimir. He has heard things. He said nothing, which is his gift.

The clue that cracked it was almost embarrassing: the staging servers believed, sincerely and wholeheartedly, that they lived in UTC. The production servers lived in local time. Everything else was identical. Which meant the variable I had been treating as scenery — the clock — was the weapon.

Time zones are the Cthulhu of computing: ancient, tentacled, worshipped entirely by accident. Nobody believes in them until one reaches up out of the deep and takes a production system with it.

A Séance Called git blame

Follow the mechanics forward and the picture assembles itself with the awful neatness of a locked-room mystery finally solved.

The mass logouts came first. The errors were just the thundering herd of evicted users stampeding back toward the door. The logouts were evictions — something was deleting live sessions. And there was only one thing in the entire system with delete rights: a cleanup job, a cron, that pruned "expired" sessions every night while the city slept.

In its WHERE clause, the definition of expired was computed with one clock, while the sessions themselves had been stamped with another. For one window every night, the two clocks disagreed about whether it was still today — and during that window, the job scanned every live session in the database, judged them all ancient beyond recovery, and deleted them. All of them. Then it tidied up, logged its success, and went back to sleep until tomorrow.

And the schedule? The cron ran at 3:00 A.M., because 3:00 A.M. is when traffic is lowest. I know this with terrible intimacy, because I scheduled it there myself, a lifetime ago, in a pull request I described — I checked — as "safe."

One clock stamps. Another clock judges. Once a night, they disagree. The rest is arithmetic wearing a hockey mask.

I ran git blame on the comparison, which is less like forensics and more like a séance: you summon the dead, you ask who did this, and the answer always arrives in monospace. The blame lit up line by line, and then it landed on a commit from 241 days ago, with a message I have now read so many times I've stopped flinching:

quick fix: make cleanup behave for now, will revisit after launch

Author: me. Reviewer: me. And one line above it, written in my exact voice, a comment: // temporary, do not let this ship as-is.

I shipped it as-is.

I want to be precise about the feeling, because "the call is coming from inside the house" is a horror trope for a reason, and the reason is that the house is you. I read that code the way you reread a text you sent at a party: with dawning, pixel-by-pixel horror, at a stranger who had my name, my typing cadence, and my signature joke in the comments. I don't remember writing it. I remember launch week — I remember being exhausted and certain, which is the same memory wearing different clothes. Somewhere between then and now, that line stopped being mine and became the system's. Then the system handed it back to me at 3:04 A.M., with interest.

The Call Was Coming From Inside the Commit

Here is the thing I understood at around five in the morning, sitting in the wreckage of my own certainty — the thing I'd stay up all night again to keep:

A bug is not an intrusion. A bug is a disagreement — the residue left where the system in your head and the system in production quietly parted ways. Alfred Korzybski said the map is not the territory; code is a map we hand to a machine, and the machine is a perfectly literal client. It never rounds up toward our intentions. It executes the gap between what we meant and what we said — at nine digits a second, forever, until somebody notices.

And the noticing is science. Genuinely, structurally science. Every suspect I ran down that night was a hypothesis — the deploy, the network, the innocent new hire — and the logs were nature's answer: no, no, and no. Karl Popper would have recognized the procedure instantly. I wasn't debugging; I was falsifying. I was doing rigorous, disciplined science all night. I just hadn't expected the final theory to be me.

That's the twist the genre keeps missing. We tell debugging stories as detective stories — find the culprit, restore order, roll credits. But there is no culprit, because the culprit is a condition: complexity plus time plus a confident human moving quickly. Edsger Dijkstra — who never once let a good sentence go unsaid — put the quiet part loudly: "Program testing can be used to show the presence of bugs, but never to show their absence." Absence of bugs cannot be demonstrated. Only deferred. Every codebase is a haunted house where all the ghosts are future versions of the people currently alive.

Douglas Hofstadter wrote whole beautiful books about systems that contain their own self-reference — loops that point at themselves and turn strange. We ship those weekly. We just call it Tuesday.

So I gazed into the stack trace, and the stack trace gazed back into me, and it used my font.

Absolution Is Not a Deploy

The fix was one line. I want that on the record, because the disproportion is both the punchline and the lesson: a week of 3 A.M. pages, four hours of dread, one small theological crisis — resolved by teaching two clocks to agree. One word, effectively. Eight months of latent catastrophe, priced at less than a tweet.

Then I did the three things that actually mattered, none of which were the line of code.

I made staging's clocks match production's, because "works in staging" should at minimum mean "works on the same planet." I added an alert on mass logouts specifically, because error rates are the scream and logouts are the whisper, and monitors that only hear screams will always fail you at exactly the volume that matters. And I wrote the post-mortem, and I put my own name in the root cause field.

That last one is the bravest sentence in engineering: I did this. Not as penance. There were no robes, no ritual shaming, no wheel-of-misfortune slide at the all-hands. Blameless doesn't mean consequence-free — it means the consequence is knowledge. The document said, plainly: a well-meaning engineer, under launch pressure, shipped a temporary change with no ticket, no reminder, and no monitoring that could have caught it for 241 days. Remove any one of those conditions and the incident doesn't happen. Which means the fix was never "be more careful." The fix is "build a system where careful is not a heroic act."

Reason's Swiss cheese, again: I wasn't the hole. I was the unlucky alignment of the holes, wearing a lanyard.

At 6:40 the sun came up, indifferent, like a build that passes on the second try. I bought the new engineer a coffee and told her she'd been acquitted of a crime she never knew she was charged with. She laughed. Then she made me show her the diff, and she asked the question every senior engineer should be asked monthly:

"Who else has a TODO like this?"

We found four. Each had my fingerprints, or hers, or someone's — because that is what fingerprints are. Evidence that a human was here. And humans are how the future pages you.

Epilogue for the Next One

The next horror story has no villain either. Statistically speaking, it's you.

Not because you're careless — because you're fast, and confident, and navigating a map so much larger than any territory one person can hold. Seniority in this field isn't the accumulation of tricks. It's the slow, expensive process of updating your priors against your own evidence. Every scar is data. Every 3 A.M. is a peer review conducted by a merciless machine.

So, some benedictions — cheap, and hard-earned:

Write TODOs with dates, not vibes. Temporary, in production, means geological. Schedule your crons at hours you have personally witnessed while awake. And mistrust any diff you'd describe as "safe" in a pull request titled "small cleanup" — the monster is always introduced as a minor character.

The bugs of the future are being written right now, by people who are certain. The only defense our species has found is humility with instrumentation: assume the map is wrong, watch the territory, and when something finally pages you at 3 A.M., skip the séance. Open the blame. Say hello to the author.

Your turn: tell me about your 3 A.M. monster in the comments. I promise I won't ask who wrote it.

The answer is always the same.

Top comments (0)