I once watched a Sentry inbox melt down. A developer had added a throw to flag a deprecated pattern. The pattern was used everywhere. Within an hour, the plan quota was burned through and real bugs stopped surfacing entirely.

The throw was the right instinct. The deprecated pattern was worth surfacing. But it wasn't a bug, and it didn't need anyone interrupted. A warning log would have done the same job: trend visible, Sentry quiet, real bugs still findable.

Choosing where the error lands matters as much as choosing to fail at all. The choice decides who pays: engineering, support, or your users.

The five places it can go

When something goes wrong, you're picking between five audiences. Each one can act on the failure differently.

Validation error. The user can fix it themselves, so tell them how. "Password must be at least 12 characters" closes the loop without anyone else getting involved. A 500 page that says "Request failed" sends them to support instead.

Generic error. The user can only retry; support reads the real story in the log. On signup, the response for "new email" and "email already has an account" should be identical: 202, "Check your inbox," same headers. The email itself diverges. A 409 for the second case quietly hands an attacker your account list, even if the body matches.

Idempotent ignore. Nobody needs to do anything. The end state the caller asked for already holds. Cancelling an already-cancelled subscription should return success and write a log line, not throw "Subscription not in active state" to Sentry every time someone double-clicks the button.

Warning log. Engineering can spot the trend when they look; nobody gets pulled in for it. A transient 503 from a third-party API that the retry handled gets logged with the endpoint and attempt count. Trends matter; individual occurrences don't. Wire each 503 to a Sentry alert and you interrupt engineering for a request that was always going to succeed on the second try.

Sentry exception. Engineering needs to act. A condition that should not have been reachable. Two payments processed for the same idempotency key. Throw, alert, investigate. The opposite mistake is swallowing "database connection refused" as a debug log because "it usually recovers." It usually does. Until it doesn't.

Pick the wrong audience and someone pays

Throw too loud and engineering starts ignoring Sentry, or you hit the plan quota and the channel goes dark. Either way, the next real bug is harder to find.

Throw too quiet and the system "works" while quietly corrupting state. By the time anyone notices, the trail is cold.

Make the message too specific and you leak internal IDs or account presence to anyone curious enough to try.

Make it too vague and customer support inherits the problem. Users open tickets they can't self-resolve. Agents reconstruct what happened by pulling in engineers. The queue piles up. I've watched support teams effectively become a translation layer for errors the system could have explained itself.