Traced live on the mini, 70+ sessions
Verified against api.anthropic.com rate-limit headers  ·  2026-08-27

Why does Claude keep
asking you to log in?

Two kinds of token, two separate stores, four places a credential hides
— and why the meter said 99% when the account was at 84%.
ANSWER  ·  LOGGING IN IS WHAT LOGS EVERYONE ELSE OUT
docs.propflowai.coCLAUDE AUTH MODEL
02 / VERDICT
The short version, before the mechanism

One session refreshing its token silently logs out every other session on that account.

Claude Code stores a login in a file with no lock on it. Refreshing rotates the token server-side, so every other copy becomes invalid the instant one session uses it.

UPSTREAM BUGS
7
open GitHub issues, same root cause
ROTATIONS · 08-26
6
each one invalidating 28–113 panes
RE-LOGINS THAT DAY
~hourly
all day, by hand
AFTER THE FIX
0
bearer tokens cannot be revoked this way
The fix is not "log in less". It is to stop using the kind of credential that can be revoked out from under a running session.
github.com/anthropics/claude-code issues #24317 #25609 #27933 #43392 #48786 #54443 #5633902
03 / MECHANISM
The whole story turns on this distinction

A session token is borrowed. A bearer token is issued.

WHAT /login GIVES YOU

The session token

Lives in ~/.claude/.credentials.json. Short-lived — about 8 hours — and kept alive by a refresh token. Using that refresh token rotates it, and every other copy of it dies at that moment. The file has no lock, so two sessions racing is normal, not exotic.
WHAT claude setup-token GIVES YOU

The bearer token

A one-year sk-ant-oat01-… string, scope user:inference. It never refreshes, so it never rotates, so nothing another session does can revoke it. Handed to a process as an environment variable at launch.
Both authenticate the same account and spend the same quota. The difference is only who can invalidate them.
~/.claude/.credentials.json · ~/.claude/.tokens/*.token03
04 / MECHANISM
The cascade, step by step

The refresh is the destructive act —
and every session does it automatically.

session A's token nears expiry
A refreshes it
server rotates the token
B, C, D… hold a dead copy
WHAT SESSIONS B–D SEE
401
OAuth access token has been revoked — on their very next prompt, mid-work.
WHAT FIXES IT FOR THEM
/login
…which rotates the token again, and kills whoever refreshed last. The cure is the disease.
This is why it felt random. You were not doing anything wrong — you were in a loop with no exit.
measured on the mini · 6 rotations, 28–113 panes each · 2026-08-2604
05 / STORES
The part that makes the dashboards confusing

There are two completely separate collections of credentials.

THE KEYCHAIN POOL

8 accounts, macOS Keychain

What ccswitch list shows and what ccswitch auto rotates. Session-token based, so it carries the revocation problem. It is also the thing most of our reporting still reads.
THE BEARER BANK

8 tokens, ~/.claude/.tokens/

Plain files, one per account, selected by the ~/.zprofile-token symlink. This is what every session actually runs on since 2026-08-26.
We migrated the fleet to the bank and left the reporting pointed at the pool. Every confusing number in the last two days comes from that one gap.
ccswitch:3896 TOKENS_DIR · ccswitch:3897 ACTIVE_TOKEN_LINK05
06 / STORES
Why the first fix reached nothing

A token has to be delivered four different ways

01 · SHELL

Login shells

Read ~/.zshenvload-token.sh~/.zprofile-token · picks up a change immediately.
02 · TMUX

The tmux server's cached environment

Captured once when the server started, then handed to every pane forever after · the layer that silently ate the first fix.
03 · DAEMON

claude daemon run

Holds whatever it was launched with and passes it to every background session it spawns.
04 · BG-PTY

bg-pty-host processes

Three were still on the credentials-file path hours after the migration.
Fix one and it looks fixed. Fix three and new panes still get the old token.
tmux show-environment -g CLAUDE_CODE_OAUTH_TOKEN · ps eww -o command= -p <pid>06
07 / THE 99%
The specific number that prompted this deck

The statusline was showing
a different account's usage.

WHAT THE METER SAID
99%
The keychain pool's active account — one this session was not using and could not affect.
WHAT THIS SESSION WAS AT
84%
Bearer account6, read live from the API's own rate-limit headers.
The code took max(live, cached) of two numbers that used to describe the same account. Since the bearer migration they routinely describe different ones — so the worse of two unrelated figures won.
ccswitch cmd_statusline · fixed in ccswitch#12 · 2026-08-2707
08 / THE 99%
Which number to trust, and when

Three meters, and only one of them is about you.

The 5-hour window
The one that stops a turn mid-flight. Refills continuously. This is the number to watch when you are actively working.
The 7-day window
The weekly budget. Resets on a rolling date per account, not on a calendar week — two accounts can be 5 days apart.
The Fable quota
A separate cap from the general one. An account can be healthy at 84% overall and completely out of Fable.
Ground truth
ccswitch doctor reads the account this session is really on, by inspecting the process rather than the shell.
A 429 is not a dead token. It means the credential is good and the account is capped — calling it dead sends you to re-mint something that works.
anthropic-ratelimit-unified-{5h,7d}-utilization08
09 / LIMITS
The constraint everything else bends around

Switching accounts changes what the next session gets. Never this one.

Why
A process's environment is fixed at exec. Repointing the symlink cannot reach inside a program that is already running.
What that means
A session keeps its token until it is re-executedtmux respawn-pane -k with --resume. The conversation survives; the process does not.
The trap
echo $CLAUDE_CODE_OAUTH_TOKEN lies. Every shell re-reads the symlink, so it shows what a restart would get — not what the session is spending.
The honest read
ps eww -o command= -p <pid> — the process's own environment, as captured at launch.
This bit us directly: a diagnostic reported the switch as already applied while the session was still billing the old account. It was reading the shell, not the session.
ccswitch session_auth_state() · fixed 2026-08-2709
10 / LIMITS
The question that started the investigation

The auto-switch was steering a wheel
that was no longer connected.

every turn boundary
ccswitch auto runs
checks the keychain pool
bearer bank untouched

Nothing was broken. Nothing was ever wired. The bearer bank had no rotation at all, so an active account reached 99% of its weekly cap with every session still pointed at it — while ccswitch list reported the pool as healthy.

Now fixed: auto evaluates the bank too, bounded to one probe per 15 minutes, with the full sweep only at an actual rotation event.
ccswitch#10 · rotate the bearer bank · merged 2026-08-2710
11 / LIMITS
Two operations that sound alike and are opposites

Rotating a bearer is free. Refreshing a login is destructive.

ROTATE THE BEARER

Repoint a symlink

Changes which token new sessions get. Touches no server state, invalidates nothing, and cannot affect a running session. Safe to do at any time, for any reason.
REFRESH A LOGIN

Rotate server-side

Mints a new token and invalidates every other copy. This is why a guard now refuses to refresh a token any live session is still holding — not refreshing costs a usage reading; refreshing costs someone their login.
The guard reports such an account as "in use by a running session — not refreshed". That is an honest unknown, not a failure.
ccswitch held_mark / held_recently · ccswitch#711
12 / PLAYBOOK
The one case where a login is still the answer

Do it where no live session can be hurt.

01 · WHERE

On the Mac, never the mini

Each machine has its own credentials.json, so a Mac login physically cannot revoke a mini session.
02 · WHY

Only to mint, not to work

Log in, then immediately claude setup-token and bank the bearer. The login is a means to a token, not a way to run.
03 · WHO

An adrift account needs its owner

"No fresh token in 17 days" means that machine stopped publishing — re-minting it elsewhere steals ownership rather than fixing it.
Day to day the answer is: don't. A bearer session that is out of quota needs a rotation and a respawn, not a login.
ccswitch pool-health --alert · per-trigger remedies · ccswitch#1112
13 / PLAYBOOK
Five fixes, all merged and deployed

The gaps between the two stores, closed one at a time

#10 · ROTATION

The bearer bank now rotates itself

Both layers updated — symlink and tmux server env. Refuses to rotate on a network error, or onto a capped account.
#10 · DOCTOR

doctor reads the process, not the shell

And says plainly when the two disagree.
#11 · ALERTS

Each alert trigger carries its own remedy

"Adrift" no longer tells you to re-mint an account that is perfectly healthy.
#12 · METER

The statusline reports this session's account

The 99% cannot appear on a bearer session again.
PropFlow-Technologies/ccswitch #10 #11 #12 · gera-propflow/local-bin #29 #3013
14 / CORRECTIONS
On the record, because each one cost time

Four wrong calls I made getting here.

Blamed my own change
Called the login cascade a regression I had shipped. Hourly data refuted it — the spike predated my binary going live.
Caused a 429 outage
Hung a full 8-call sweep off a 60-second timer. Four accounts rate-limited. Fixed the same day.
Declared it fixed twice
Once when the tmux server still held the old token, once reading the shell instead of the process.
Said "never emitted"
Claimed a review marker had never worked, from a 7-PR sample that happened to exclude the three where it had.
The pattern is the same every time: a confident negative from an instrument that could not see the counterexample.
this session · 2026-08-26 to 2026-08-2714
15 / CORRECTIONS
The design calls that held up under pressure

Three things were right before today, and stayed right.

THE BEARER CALL

Moving off logins

Proven by deleting credentials.json outright and watching a bearer session keep working.
THE HELD GUARD

Never refresh a held token

Costs a usage reading. Saves someone's login. The right trade, and it reports the unknown honestly.
IDLE IS FREE

Sessions cost nothing at rest

~70 alive, 0 established connections, 0 credential writes in 90 seconds. Leaving them open is not the problem.
measured on the mini · 2026-08-2615
16 / CLOSE

Logging in was never
the fix. It was the cause.

Two stores, one migrated and one still doing the reporting.
Every confusing number came from that gap — and it is closed now.
docs.propflowai.co/a/claude-auth-modelPROPFLOW · CLAUDE AUTH MODEL

All slides

PropFlow Docs