← All posts

The Stale Pointer That Looked Like a Dead Login

A Mac in my Claude Code fleet failed auth after a reboot. The tokens were fine — the file the binary actually reads was three days behind.

  • claude-code
  • credentials
  • debugging
  • automation
  • ops

A Mac in my Claude Code fleet came back from a reboot and refused to work. “Claude credential not correct.” Every session on it, dead on arrival.

If you’re not knee-deep in this: I run a bunch of machines that each drive Claude Code — Anthropic’s command-line coding agent — and I rotate between several accounts so the work spreads across them. Each account has its own saved login. When a machine says its credential is wrong, the obvious read is that the login expired and you need to sign in again.

So my hand went straight to claude login — the full sign-in flow, the browser dance, re-authorizing everything. That’s the tempting move. It’s also the wrong one, and I almost did it.

Here’s what stopped me. Re-logging-in is expensive: it can invalidate the tokens that are currently working on the other machines sharing that account. If the tokens were actually fine, I’d be breaking three things to fix one. So before touching auth, I did the boring thing and looked at file timestamps.

The claude binary reads one file: ~/.claude/.credentials.json. Think of it as a sticky note on the fridge that says “use this login right now.” My rotation system keeps the real saved logins somewhere else — one file per account. When you switch accounts, a script copies the right per-account file over to that sticky note.

The sticky note was three days old. The per-account files? Fresh — refreshed automatically since the last switch.

That was the whole bug. Nothing expired. The tokens in the per-account file were valid. But there’s no boot hook that re-copies the active per-account file onto the global sticky note. So any token refresh that happens between switches updates the source and leaves the pointer behind. A reboot doesn’t fix it — the machine just reads a stale note and reports a credential that no longer matches.

The fix was one command: re-run the account switch for the account that was already active. That rewrites the global file from the fresh per-account source. Auth worked instantly. No login, no browser, no invalidating anyone else’s tokens.

The 30-second diagnostic give me the detail

Compare the read-path file against the source before assuming expiry:

# what the binary reads:
ls -l ~/.claude/.credentials.json
# the per-account sources:
ls -l /opt/claude/config/*/.credentials.json

# if global is older than the active account's file, just re-point:
claude-account switch <active-account>

The trap: a reboot feels like a token event, so you reach for claude login. But nothing about a reboot expires an OAuth token — it just re-reads whatever file is on disk. Timestamp divergence is the tell.

The general shape here: any time you’ve got a pointer file and source files, and the source can update without the pointer knowing, you’ve built a place where staleness hides. When the pointer-reader complains, check whether the pointer and the source disagree before you assume the source itself went bad.

Diff the timestamps first. It’s thirty seconds, and it saves you from breaking the thing that was never broken.