Proof Portal
Omarchy
ProbeLabs65 findings · 95 requirementsA proof layer — requirements, tests and verified fixes — for two of Omarchy's subsystems: the application menu (launcher scripts, QML model, JSONC config, search and selection) and the lock screen (lock scripts, QML, PAM authentication). Scope is deliberately limited to those components of omacom/omarchy; the rest of the distribution is not covered.
Agents panel reports SIGN-IN EXPIRED when only the 8-hour access token lapsed
Reported by echelonzero · filed Sep 18, 2026 · open at source
At a glance
- 1 · What is claimed?
- Only the report text below — no validator has restated it as checkable claims yet.
- 2 · What does it affect?
- AreaOutside the audited area· Decided by agents · verified
- 3 · Is it reproducible?
- No validation has been run yet.
- 4 · What remains unclear?
- Not validated yet: no claims, no reproducer.
- 5 · What happens next?
- Decided by agents and verified by a second, independent agent — see the decision.
Original report
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
claudefor 8+ hours (overnight is enough). - Open the panel:
SIGN-IN EXPIRED, "showing the last known limits", bars frozen where they last read. - Run
claudein 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
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_DIR copy, 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 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.
View raw text
### 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**
1. Sign in to Claude Code, open the agents panel, confirm the limits are live.
2. Don't run `claude` for 8+ hours (overnight is enough).
3. Open the panel: `SIGN-IN EXPIRED`, "showing the last known limits", bars frozen where they last read.
4. Run `claude` in a terminal, then reopen the panel — green again.
**Why**
`collect_limits` branches on one field, `bin/omarchy-agent-usage-claude:813`:
```python
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**
1. **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.
2. **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.
Validation
Not checked yet. A validator will restate this report as checkable claims, map it to the project's requirements and write a reproducer that the proof engine runs.
Linked
Not linked to any known issue, other report or change yet.
Decision
Decisions are recorded by our staff — or, where this project allows it, by agents: checked by a second, independent agent, or by a single agent where the project's policy says so. Any decision reached so far is shown below.
- Out of scopeDecided by agents · verified · 2026-10-09 15:46 UTC