DEV Community

Cover image for Your Error Messages Are a Feature: What a "Random Logout" Bug Taught Me About Designing for Failure
Yusuf Saifurahman
Yusuf Saifurahman

Posted on AI-assisted

Your Error Messages Are a Feature: What a "Random Logout" Bug Taught Me About Designing for Failure

In my last article, I wrote about UX bugs that pass every test — like a wallet with no visible balance. This one is about the biggest category of those bugs: what your app says, or doesn't say, when something goes wrong.

The complaint came in as: "The app keeps logging me out randomly."
Random logouts are a scary bug report. They suggest session corruption, cookie issues, maybe something security-shaped. So we dug in and found that nothing was random at all.

The portal used short-lived access tokens with a refresh flow. The access token expired on schedule, the frontend's refresh call was broken, the refresh silently failed, and the app dumped the user back to the login screen. Mid-task. Half-filled form, gone. No message. No "your session has expired." Just login screen.

Here's the uncomfortable part: from the system's point of view, almost everything was behaving correctly. Token expired? Terminate the session. That's textbook security. The refresh bug was real and we fixed it, but the deeper problem would have existed even with a working refresh flow: the app had no plan for telling the user what happened.

The user experienced a malfunction. The system experienced a Tuesday. The entire difference between those two experiences is communication and communication during failure is a feature you have to build, not a byproduct you get for free.

Errors are where trust is won or lost

Think about when users actually form opinions about your software. Not during the happy path. The happy path is invisible when it works. Opinions form at the moments of friction: the failed payment, the rejected form, the expired session, the timeout.

In fintech this is amplified. A user who gets silently logged out of a dashboard where their money lives doesn't think "session management edge case." They think "can I trust this platform?" One unexplained failure does more damage than the feature did good.

Yet on most projects I've audited, error handling is the least designed part of the product. It's written last, by the developer, in the ten seconds between catching the exception and moving on:

catch (error) {
  alert("Something went wrong");
}
Enter fullscreen mode Exit fullscreen mode

Or worse the error is "handled" by passing it straight through:,

Request failed with status code 422

I found exactly that string displayed to end users on the same portal. Technically handled: the app didn't crash, something was displayed. But "handled" and "helpful" are different bars. That message is written in the developer's language, about the developer's world (HTTP semantics), for an audience of one and that one person isn't the user staring at it.

The four questions every error message must answer
A useful error message is not an apology. It's a tiny piece of UI with a job description:

  1. What happened? In the user's vocabulary, not the protocol's. "Your session expired" not "401 Unauthorized." "That phone number format isn't recognized" not "validation failed."

  2. Is my stuff okay? Did the payment go through or not? Was the form saved? Ambiguity here is what causes double-submissions and duplicate transactions. If money or data is involved, state its status explicitly.

  3. What do I do now? Retry? Fix a field (which one)? Wait? Contact support (how)? An error message with no next step is a dead end wearing an explanation.

  4. Whose fault was it? Not to assign blame to set expectations. "We're having trouble reaching the server" tells the user their input was fine and retrying might work. "Please check the highlighted fields" tells them the fix is in their hands.

Run the 422 message through those questions: it answers none of them. Run "your session has expired, log in again to continue; your draft has been saved" through them: it answers all four in fourteen words.

Fixing the logout: designing session expiry instead of suffering it
The repaired version of that session flow ended up with three layers, and none of them required a designer just the decision that expiry is a scenario, not an exception:

  1. Refresh silently while you can. A response interceptor catches the 401, attempts the refresh, and retries the original request. The user never knows. (This is also where the original bug lived the interceptor's failure path had a bug that made every expiry terminal.)
api.interceptors.response.use(null, async (error) => {
  if (error.response?.status === 401 && !error.config._retried) {
    error.config._retried = true;
    const ok = await tryRefresh();
    if (ok) return api.request(error.config);
    onSessionExpired(); // <- the part nobody builds
  }
  return Promise.reject(error);
});
Enter fullscreen mode Exit fullscreen mode
  1. When refresh fails, say so in place. onSessionExpired() doesn't hard-redirect. It opens a modal: "Your session has expired. Log in again to continue where you left off." The page state stays mounted behind it.

  2. Protect the user's work. Unsaved form input gets stashed (in memory or local draft storage) before re-auth, and restored after. Losing a half-completed form to a security policy is the app punishing the user for the app's own token lifetime.

The security posture is identical to the original. The experience went from "this app randomly logs me out and eats my work" to a non-event. Same backend, same tokens the entire difference is that failure got designed.

A 20-minute error audit for your current project
You don't need a redesign to fix most of this. Open your app and deliberately break things:

  • Submit a form with invalid data: does the message name the field and the fix?

  • Kill your network mid-action: does the app distinguish "you're offline" from "we broke"?

  • Let a session expire mid-task: message, or mystery redirect? Does your draft survive?

  • Force a server error: does the user see a human sentence or a status code?

  • Double-click a submit button during a slow request — one transaction or two?

Then grep the codebase for your error strings. If you find "Something went wrong" more than a couple of times, or any raw error.message rendered to the UI, you've found your backlog.

Put it in the definition of done

The reason error UX is chronically bad isn't that developers don't care it's that nobody makes it a requirement. "User can log in" gets a ticket; "user is told clearly when their session ends, and loses no work" never does. So it never gets built, and it never fails QA, because you can't fail a requirement that doesn't exist.

The fix is boring and effective: every feature ticket gets an error path line in its acceptance criteria. What does the user see when this fails? Write the actual message copy in the ticket if you can it takes two minutes and it forces the conversation.

Your error messages are the part of your product users read most carefully, at the exact moment they trust you least. Write them like it.

Found a great (or horrifying) error message in the wild? Drop it in the comments worst one wins.

Top comments (1)

Collapse
 
suppdevbot profile image
DEV SUPPORTS •
You need to verify your account.
Enter fullscreen mode Exit fullscreen mode

tr.ee/dev-to