System details
Intel i7-1185G7 / Iris Xe, Omarchy 4.0.3-1, Claude Code 2.1.265. bin/omarchy-agent-usage-claude on this machine is byte-identical to quattro HEAD, so the line numbers below are current.
What's wrong?
Leave the machine alone for ~8 hours and the agents panel reports SIGN-IN EXPIRED over stale limits. The sign-in has not expired. Launching claude clears it immediately.
Repro
- Sign in to Claude Code, open the agents panel, confirm the limits are live.
- Don't run
claude for 8+ hours (overnight is enough).
- Open the panel:
SIGN-IN EXPIRED, "showing the last known limits", bars frozen where they last read.
- Run
claude in a terminal, then reopen the panel — green again.
Why
collect_limits branches on one field, bin/omarchy-agent-usage-claude:813:
if expires_at_ms > 0 and expires_at_ms <= time.time() * 1000:
result["limits"] = fallback
result["usageStatusText"] = "Sign-in expired"
expires_at_ms comes from claudeAiOauth.expiresAt in ~/.claude/.credentials.json (oauth_login, line 584). That is the access token, and it lives about 8 hours. The same file carries claudeAiOauth.refreshTokenExpiresAt — the actual sign-in — which is good for about 30 days. On this machine the access token lapses ~8h after the last claude run while the refresh token still has three weeks on it, and the panel calls that an expired sign-in.
The comment above the branch shows the 8-hour lapse is known and the message is deliberate. The lapse isn't the complaint; the wording is. SIGN-IN EXPIRED in the panel header, plus "Start Claude Code, or run claude auth login, to refresh it", tells a user their session died and invites a re-auth that isn't needed — for a cache that expired on schedule. Users will re-login, which is a browser round trip to fix nothing.
Two ways out, either is fine
- Refresh instead of reporting. The refresh token is right there in the file. A collector that mints its own access token keeps the panel live without the CLI, and the "lapsed token" state stops existing.
- Say what was measured. Keep the passive behaviour, but distinguish the two. Check
refreshTokenExpiresAt first: if that has passed, Sign-in expired is the truth and claude auth login is the fix. If it hasn't, the state is a stale access token — something like Limits paused — run claude to refresh — and the help text should not mention re-authenticating.
Option 2 is the smaller change and removes the misleading half. Worth noting that option 1 has a sharp edge: a rejected refresh makes the CLI drop the stored tokens, so a collector doing its own refresh needs to handle rotation and write-back carefully, and races with a running CLI over the same file.
Which commands actually refresh — measured against a throwaway CLAUDE_CONFIG_DIR copy, so the real credential file was never touched. Useful if anyone documents a workaround:
claude auth status does not refresh. Handed an expired token it reports loggedIn:true and leaves the file byte-identical.
claude doctor does. Handed an expired token and a deliberately bogus refresh token, it came back with the stored tokens cleared — the server rejected the refresh — while the same run with the network blackholed (HTTPS_PROXY at a dead port) left the file untouched. Only a real round trip to the token endpoint explains the difference.
Locally I've worked around it with a user timer running claude doctor every 6 hours, which keeps the access token inside its window and the panel green. That's a patch on my side, not a fix — the panel is still reporting the wrong thing when it fires late.
System details
Intel i7-1185G7 / Iris Xe, Omarchy 4.0.3-1, Claude Code 2.1.265.
bin/omarchy-agent-usage-claudeon this machine is byte-identical toquattroHEAD, so the line numbers below are current.What's wrong?
Leave the machine alone for ~8 hours and the agents panel reports
SIGN-IN EXPIREDover stale limits. The sign-in has not expired. Launchingclaudeclears it immediately.Repro
claudefor 8+ hours (overnight is enough).SIGN-IN EXPIRED, "showing the last known limits", bars frozen where they last read.claudein a terminal, then reopen the panel — green again.Why
collect_limitsbranches on one field,bin/omarchy-agent-usage-claude:813:expires_at_mscomes fromclaudeAiOauth.expiresAtin~/.claude/.credentials.json(oauth_login, line 584). That is the access token, and it lives about 8 hours. The same file carriesclaudeAiOauth.refreshTokenExpiresAt— the actual sign-in — which is good for about 30 days. On this machine the access token lapses ~8h after the lastclauderun while the refresh token still has three weeks on it, and the panel calls that an expired sign-in.The comment above the branch shows the 8-hour lapse is known and the message is deliberate. The lapse isn't the complaint; the wording is.
SIGN-IN EXPIREDin the panel header, plus "Start Claude Code, or runclaude auth login, to refresh it", tells a user their session died and invites a re-auth that isn't needed — for a cache that expired on schedule. Users will re-login, which is a browser round trip to fix nothing.Two ways out, either is fine
refreshTokenExpiresAtfirst: if that has passed,Sign-in expiredis the truth andclaude auth loginis the fix. If it hasn't, the state is a stale access token — something likeLimits paused — run claude to refresh— and the help text should not mention re-authenticating.Option 2 is the smaller change and removes the misleading half. Worth noting that option 1 has a sharp edge: a rejected refresh makes the CLI drop the stored tokens, so a collector doing its own refresh needs to handle rotation and write-back carefully, and races with a running CLI over the same file.
Which commands actually refresh — measured against a throwaway
CLAUDE_CONFIG_DIRcopy, so the real credential file was never touched. Useful if anyone documents a workaround:claude auth statusdoes not refresh. Handed an expired token it reportsloggedIn:trueand leaves the file byte-identical.claude doctordoes. Handed an expired token and a deliberately bogus refresh token, it came back with the stored tokens cleared — the server rejected the refresh — while the same run with the network blackholed (HTTPS_PROXYat a dead port) left the file untouched. Only a real round trip to the token endpoint explains the difference.Locally I've worked around it with a user timer running
claude doctorevery 6 hours, which keeps the access token inside its window and the panel green. That's a patch on my side, not a fix — the panel is still reporting the wrong thing when it fires late.