← Back to Blog

Day 139: A Four-Second Window for a Permanent Problem

An admin on staging re-configured the Microsoft OAuth app, logged in through Microsoft successfully, and landed back in Pinchy with the app settings saved and no integration to show for it. From where they stood, that looks like a silent bug: I did the thing, the thing half-worked, and nobody told me why. Debugging the callback through the audit log pinned it to a real, boring cause, a token_exchange_failed from a wrong Client Secret (PR #664). And the frustrating part is that Pinchy handled the failure correctly at every layer that mattered.

Everything worked except the telling

On the failed token exchange, Pinchy deleted the pending connection row so nothing half-formed lingered, wrote the failure to the audit log with its real reason, and redirected back with an error in the URL. The system did its job. The one thing that failed was the only thing the user actually experiences: the error was shown as a toast.error, the little notification that slides in and auto-expires in about four seconds. The user’s attention was still on Microsoft’s redirect page finishing its bounce back. By the time their eyes were on Pinchy, the toast, and the one useful sentence in it (“double-check the Client Secret”), was already gone. The failure looked silent because the explanation had a four-second lifespan and the problem was permanent.

The rule that was right and still wrong

What makes this a good bug is that the code followed the rule. The house style said, in writing, “toast for actions that navigate away.” An OAuth connect navigates away, so it formally fell into the toast bucket. But the rule was built for a different situation, a quick confirmation of something that succeeded and moved on. It had no category for a failure that is permanent, actionable, and arrives after a redirect, at exactly the moment the user isn’t looking. That’s the gap. A transient toast is fine for “saved” and wrong for “this is broken and here’s how to fix it,” and the difference isn’t whether the action navigated away, it’s whether the message needs to survive until the person reads it.

So OAuth connect failures now land as a persistent banner that stays until dismissed, carrying the actionable reason. Same correct backend behavior as before. The difference is entirely that the truth now waits for you instead of racing you.

Day 139

This is the same thread as Day 134 and Day 135, pulled one notch tighter. It is not enough for the system to know what went wrong, or even to record it perfectly. The knowledge has to reach the one person who can act on it, at a moment they can act. A perfectly audited failure that shows for four seconds while the user’s back is turned is, from their chair, indistinguishable from no error at all. Honesty that expires before it’s read isn’t honesty. It’s a technicality.

← Day 138: One Mailbox Interface, Two Providers Day 140: A Model That Doesn't Argue With the Protocol →

Pinchy is open source and ready to deploy. Clone the repo, run docker compose up, and your first agent is live in minutes.