One of my dev boxes came up with a dead Claude account. Not rate-limited, not throttled — the credential itself was rejected. The account file was there in /opt/claude, the agent could read it, and every request bounced.
So I did the obvious thing: I ran the login flow again. Claude Code pops open the usual OAuth handshake — the “click here to authorize” dance that hands your machine a fresh token, the same way logging back into a website re-issues your session. It completed cleanly. Green checkmarks. And the account was still dead.
That’s when I actually looked at the credential file instead of assuming it was interchangeable.
Most of my accounts carry a normal short-lived token: it expires in an hour or so and quietly refreshes itself using a refreshToken stored alongside it. Think of it like a hotel key card that the front desk keeps renewing. But this one account was different. Its expiresAt said the year 3024, and its refreshToken was null. It wasn’t a key card that gets renewed — it was a one-time-minted master key, cut once, with no machine anywhere that knew how to cut another.
Here’s the part that cost me an hour. When you re-run the login flow, it doesn’t restore the token you lost. It mints a new one — and it mints the ordinary short-lived kind. So logging in “worked,” in that it produced a valid credential, but it produced the wrong species of credential. The special long-lived token can’t be regenerated from the login flow at all. It only ever existed as that one original file.
The real fix was almost dumb. Another box in the fleet — a GPU machine — still had the original credentials-<account>.json, alive and untouched. I scp’d the file straight over, dropped it into /opt/claude, and the account came back instantly.
How to tell which kind of credential you're holding give me the detail
Before you assume login will fix a dead account, read the file. On any box in the fleet:
jq '{expiresAt, hasRefresh: (.refreshToken != null)}' \
/opt/claude/credentials-<account>.jsonTwo outcomes:
hasRefresh: true, near-futureexpiresAt→ regenerable. Re-auth is safe; it’ll refresh normally.hasRefresh: false,expiresAtin the year 3024 → irreplaceable. Do NOT run the login flow expecting a restore — it mints a different short-lived token. Copy the file from a host that still has the live original:
scp gpu-box:/opt/claude/credentials-<account>.json /opt/claude/Keep a known-good copy of the long-lived ones somewhere you can scp from, and treat that file like the artifact it is — because there’s no re-minting it.
The lesson I carry out of this: not every credential is regenerable from its login flow. Some are one-time-minted artifacts, and the only thing that restores them is the bytes of the original file. Before you trust “just log in again” to heal a dead account, check whether the thing you lost can even be re-created — or whether your only backup is a live copy on some other machine you haven’t touched yet.