A user wrote in: "I tried to restore my backup and it keeps saying my passcode is wrong. I'm 100% sure it's right." They weren't wrong to be sure. The passcode was fine. My error handling was lying to them.
Lockboxy (the encrypted vault app I build) lets you back up your vault to a file and restore it later — new phone, reinstall, whatever. Restoring needs your passcode (or a recovery key) to decrypt the backup. The restore screen's error handling looked like this, roughly:
try {
await restoreFromBackup(file, credential);
} catch (e) {
setError('Incorrect passcode. Please try again.');
}
Simple. Also wrong. That catch block fires for any failure during restore — wrong passcode, sure, but also a corrupted backup file, a backup from an incompatible app version, a truncated file from a failed download, a file that isn't a Lockboxy backup at all. Every single one of those got the same message: blame the passcode.
So someone with a perfectly correct passcode and a backup file that got mangled in transit would sit there, re-typing a passcode they already know is right, told every time that they're wrong. There's no worse debugging experience than an error message confidently pointing at the wrong cause.
The fix wasn't complicated — it just required actually having distinct error types instead of one catch-all:
try {
await restoreFromBackup(file, credential);
} catch (e) {
if (e instanceof IncorrectPasscodeError || e instanceof IncorrectRecoveryKeyError) {
setError(
usingRecoveryKey
? "That recovery key doesn't match this backup."
: "Incorrect passcode. Please try again."
);
return;
}
// Anything else — corrupted file, bad format, whatever — is NOT a credential problem.
setPickError("This backup file couldn't be read. It may be corrupted or from a different app.");
setFile(null);
}
The actual fix is almost boring: instanceof checks on a proper error hierarchy (CryptoServiceError → IncorrectPasscodeError, IncorrectRecoveryKeyError, and others) that already existed in the crypto layer. The bug wasn't a missing feature, it was a UI layer that caught everything and collapsed it into one message because that was the easy path.
Here's the part that makes this more than a generic "handle your errors properly" lesson: Lockboxy is zero-knowledge. I don't hold your passcode, I can't reset it, and if you truly forget it, your data is gone — that's the whole point of the security model. Which means when a user sees a credential error, the ONLY two things that can possibly be true are "I mistyped it" or "I forgot it." There's no third option, no "contact support to reset it," nothing. The support cost of a misleading error message is much higher here than in an app where someone can just click "forgot password." If my error message blames the passcode and it's actually the file, the user has no way to find that out themselves — they'll assume their data is unrecoverable and give up, when the actual fix might be as simple as re-downloading the backup file.
Lesson, stated plainly: if your app can't offer a human a way around a problem, your error message is the only diagnostic tool they have. It has to be right, not just plausible.
Top comments (0)