Skip to content
Kevin Dang
July 11, 2026

Reopening “cold cases” with AI agents

Everyone's using AI to build new things. I've found it's just as good at reopening the old bugs you patched but never really solved.


Does anyone remember fixing a tricky bug in the past at work but never really pinning down the “why”?

You take a look back and your solution sort of works and, more importantly, it's been surviving. But you realise it was arrived at through rounds of console-log debugging, stitched together from multiple Stack Overflow posts and other snippets from online forums. It feels like it's just one sucker punch away from being knocked down. You couldn't explain the reasoning behind every line if you tried, but you can still remember the problem you were up against.

Lately I've been using AI agents to go back and tackle these bugs properly — and it's been oddly satisfying. It feels like reopening a cold case that's been lingering for far too long.

Recently for me, that “cold case” has been taking a look at our refresh token setup and correcting it.

Exhibit A: Auth0 Refresh Tokens

We had attempted to set up Auth0's refresh tokens in our platform a couple of years ago. The idea behind refresh tokens is to create a better experience for authenticated users by cutting down how often they get challenged to re-authenticate. When a user authenticates themselves on your application, they receive an access token which is like a timed “entry pass” they can use to perform authenticated actions e.g. fetch their profile settings, make changes to these settings, etc. Since this token is presented on every single API request, it's at risk of being leaked somewhere due to its natural exposure. Owing to this, you generally want to set your access token expiration to be short-lived.

To improve UX, you can employ refresh tokens so that when a user's access token expires, your application exchanges its refresh token for a new access token behind the scenes, so the user never notices.

💡 Aside, since it's not part of this bug but worth knowing: Auth0 takes refresh tokens a step further with rotation. With rotation enabled, every exchange returns a new refresh token alongside the new access token, and the old one is immediately invalidated. If a bad actor intercepts a refresh token and tries to exchange it, Auth0 sees an already-used token being replayed, treats it as a compromise, and invalidates the entire token family — forcing everyone, attacker and legitimate user alike, to re-authenticate.

The symptom

For us, there was an issue in the basic refresh token flow. The behaviour we wanted was simple: if a user went inactive for 15 days, they'd get logged out and see the login page again. The way this works is that refresh tokens also have an expiration. When the short-lived access token expires, we use the refresh token to exchange for a new access token; but if a user isn't active on the platform, that exchange never takes place. When the refresh token itself expires, it can't be used — so when the user returns to the platform after that expiry, they'll get challenged to authenticate again.

For us, the challenge came a lot earlier than 15 days — always after the weekend actually. It was one of those bugs where it's not frustrating enough to raise concern, and could pass off as an intentional product choice. But for someone who was part of the implementation, it was always something that was at the back of my mind.

The problem

I remember spending quite a lot of time reading the Auth0 docs to get a grasp of refresh tokens because I thought I had missed something so fundamental.

What made it so hard to pin down was that the setup mostly worked. It wasn't broken in an obvious, every-time way — we'd often come back the next working day and get straight in without a login screen. It was only after a longer gap, like a weekend, that we'd be forced to log in again. A bug that only shows up sometimes is much harder to trust your own reasoning about.

I booted up Claude Code, explained the issue and let it do its thing. It very quickly pointed out that our auth interceptor was logging users out the moment they hit a 401 (Unauthorized), instead of attempting a token refresh first.

At the time, a 401 genuinely looked like a dead session. Every API request already ran through a function that fetched an access token before firing, so we assumed the outgoing token was always fresh. If a request still came back 401 despite that, the only explanation we could see was that the session itself was gone — so logging the user out felt like the correct response.

To see why, it helps to follow a single request on its way out.

Deep dive

Before firing anything, our code asks for an access token — and that ask passes through two checkpoints. Here's the important part: both of them judge whether the token is still good by looking only at what they already hold locally. Neither of them ever asks the server.

The first checkpoint was our own. Our wrapper had a small “has this token expired yet?” check, and if it decided the token was still valid, it handed back the cached copy and didn't even bother asking Auth0. The trouble was that our check had no buffer — it only counted the token as expired the very instant its expiry timestamp passed. So a token with a couple of seconds left on the clock sailed straight through as “fine”.

If our check did decide to ask Auth0, the request reached the second checkpoint: Auth0's own getTokenSilently. This is the function that actually trades a refresh token for a fresh access token once the old one expires — the thing we'd quietly assumed was keeping us logged in. But it makes the same kind of call, from its own cached copy of the token. If it reckoned the token was still valid, it just returned it too. No exchange, no refresh.

💡 Btw, we had to build a wrapper on top of Auth0's getTokenSilently because too many getTokenSilently calls can cause a blank screen on the page.

So the token goes out — waved through by one or both checkpoints as perfectly healthy — and the server takes one look and rejects it with a 401. Maybe the clock had skewed by a second, maybe a signing key had rotated, maybe it expired between our check and the server's. Whatever the reason, the crucial detail is that Auth0 never finds out. It doesn't see our API responses; it has no idea one of them just came back 401. As far as it's concerned, it did its job and handed over a valid token.

Which leaves the 401 landing in the one place with no context to make sense of it — our auth interceptor. The interceptor's read was simple, and wrong: "the server says no, so this person must have lost access." So it logged them out.

The fix

The fix turned out to be a small shift in where we treated the session as dead. Instead of logging the user out the instant we saw a 401, we now try to refresh the access token and replay the original request. Only if that refresh itself fails do we log the user out.

That one change moves the “dead session” signal to the right place. A 401 was never proof the session was gone — it just meant the token we sent wasn't accepted, which is exactly the situation refresh tokens exist to handle. The genuine signal that a session has ended is a failed refresh: that's when the refresh token really is expired or revoked and there's nothing left to fall back on. So that became the only condition under which we log someone out.

The speed at which it figured this out was pretty humbling. A couple of minutes vs. what was probably weeks of sustained effort at the time, without us actually reaching a conclusion.


I'd recommend taking some time to seek out your team's cold cases and pass them through an AI lens — to first see how they diagnose the issue and what they recommend to fix it (properly). Better yet, if there are bugs sitting in your team's backlogs, you now have an assistant to help churn through them so much quicker.

I feel like right now, everyone reaches for AI to build new things. But going back through old, awkward bug fixes you never fully understood? That feels like a really good application of AI in the work setting.


Read more writing →