The Worst Error Message I Ever Saw
Years ago, buried in a legacy codebase, I found an error message that read simply:
ERROR: An error has occurred.
No code. No hint about what went wrong, where, or even when. It wasn’t a placeholder comment or a TODO. It was the final, shipped, production message. If you were unlucky enough to see it, you were on your own.
I think about that line sometimes. Someone wrote it, tested it (or didn’t), merged it, and moved on. They probably had a deadline. They probably knew they’d come back to it later. They probably never did.
Error Messages Are User Interfaces
Here’s something we don’t talk about enough: error messages are the UI of your software. They’re what your users see when things go wrong — which is often the moment they need help the most.
And yet, we treat them like afterthoughts. We write them in haste at 5 PM on a Friday. We stuff in a generic message because we’re not sure what went wrong. We assume the user will check the logs (they won’t).
A good error message is an act of empathy. It says: I know you’re frustrated. I know you didn’t want this to happen. Here’s what I can tell you to help you fix it.
The Anatomy of a Useful Error
Through years of sysadmin work and debugging at ungodly hours, I’ve come to believe a useful error message has three layers:
1. What Happened
Be specific. “Connection refused” is not the same as “DNS lookup failed” and neither is the same as “SSL handshake timed out.” Each one leads to a completely different fix.
# Bad
"Connection failed."
# Better
"Could not connect to database at db.internal:5432: connection refused."
# Best
"Could not connect to PostgreSQL at db.internal:5432: connection refused (ECONNREFUSED). Verify the service is running and port 5432 is not blocked by a firewall."
2. Why It Happened
This is where you show your work. Even if you’re not 100% sure, a probable cause gives the user a starting point instead of a black hole.
3. What to Do Next
This is optional but transformative. A short suggestion — “Try restarting the container” or “Run systemctl status nginx to check if the service is active” — can cut hours out of a debugging session.
The Emotional Context
What we often miss is the emotional weight of encountering an error. The person reading your error message might be:
- A junior developer seeing their first production alert
- A sysadmin paged at 3 AM who hasn’t had coffee yet
- A user who just lost an hour of unsaved work
- Someone on their phone, trying to fix things remotely
None of them are in a state to interpret a cryptic hex code or a 50-line stack trace with no guidance. The error message is your chance to be the calm, helpful voice in the room.
I’ve started asking myself: if I were reading this message on my phone in a parking lot while my kid played in the background, would I know what to do next? If the answer is no, the message isn’t done yet.
The Curse of Knowledge
The biggest enemy of good error messages is knowledge asymmetry. When you wrote the code, you know what every variable does, what every function expects, what every edge case looks like. The person reading your error message knows none of that.
This is why “shouldn’t happen” is such a terrible error message. It assumes the user shares your context. They don’t. They see a failure. You see a path through code you wrote months ago. Bridging that gap is the craft.
On the Poetry of Logs
There’s something to be said for the opposite end of the spectrum too. A well-crafted log line — one that tells a story about what the system was trying to do, what it found, and what it chose — has its own quiet beauty.
2024-03-12 14:22:07 INFO Backup started: daily-incremental
2024-03-12 14:22:08 INFO Lock acquired: /var/lock/backup-daily.pid (pid:18432)
2024-03-12 14:22:15 INFO Source: 2.4 GB across 48,000 files
2024-03-12 14:45:33 INFO Backup complete: /mnt/backups/daily/2024-03-12.tar.gz
2024-03-12 14:45:33 INFO Duration: 23m 26s
Read top to bottom, that’s a tiny story: beginning, struggle, resolution. It’s the system telling you it’s okay, the backup ran, you can go home.
Good logging is generous. It tells the story of the system honestly — including the failures.
A Checklist for Your Next Error Message
If you take one thing from this essay, let it be this checklist I keep taped mentally above my editor:
- Specific over generic. “File not found” → give the path. “Permission denied” → say what permission.
- Present tense, active voice. “Could not connect” not “Connection was failed by the module.”
- One error, one message. Don’t bundle five failures into a paragraph.
- Include error codes — but explain them. A code alone is a dead end; a code with a one-line explanation is a breadcrumb.
- Suggest a next step. Even something as simple as “Check the logs at /var/log/myapp/” is better than silence.
The Human Thing
In the end, writing useful error messages comes down to something simple: you are writing for a person, not for a system. The computer doesn’t need empathy. The person on the other side of that terminal does.
Next time you catch yourself writing “Something went wrong” or “Unexpected behavior”, pause. Take a breath. Picture the person who will read this message, probably tired, probably under pressure, probably already annoyed that they have to be on your software at all.
Then give them something useful. It costs you nothing. It might save them hours.
And if you ever find yourself tempted to ship just:
ERROR: An error has occurred.
Remember: future-you will have to debug it. And future-you deserves better.
What’s the best (or worst) error message you’ve ever encountered? I collect them like strange stamps. Drop a comment or find me online — I’d love to hear your stories.
Leave a Reply