StatusLine: usage meters silently lose the scoped-model pill on a multi-item keychain - #2220
Open
theNetworkChuck wants to merge 2 commits into
Open
theNetworkChuck wants to merge 2 commits into
theNetworkChuck wants to merge 2 commits into
Conversation
On macOS the keychain can hold more than one item under the service name "Claude Code-credentials", told apart by account. Claude Code 2.1.278 leaves a legacy acct="claude-code-user" item whose accessToken and refreshToken are empty strings beside the live acct="$USER" item it refreshes each session. `security find-generic-password -s "Claude Code-credentials" -w` names only the service, so it returns whichever item the keychain yields first. Where that is the stub, every /api/oauth/usage fetch authenticates with an empty bearer token and fails. The failure is silent. The cache never lands, \_data\_age passes USAGE_HARD_EXPIRY, and the native-payload branch simply stops enriching: the scoped per-model pill and the EXT indicator vanish while 5H and WK keep rendering from stdin, so the line looks healthy. Observed in the wild for four days before anyone noticed the missing pill. Probe the cached winner, then $USER, then claude-code-user, then the bare lookup, and take the first item carrying a non-empty token. If all of them come back empty the layout has moved again, so enumerate the items under that service and try each; that costs one dump-keychain and only on a layout change, because the winning account is cached in /tmp/pai-kc-acct-$USER. A fetch that returns nothing usable clears that cache so the next render re-probes instead of retrying a dead credential.
When the native rate_limits payload supplies 5H and WK, the scoped per-model window and the EXT indicator still come from the OAuth cache. Past USAGE_HARD_EXPIRY that enrichment is skipped, and the scoped pill is simply not drawn. An omitted pill is indistinguishable from an account that has no scoped window at all, so a fetch that has been failing for days renders as a perfectly healthy status line. That is how the keychain bug in the previous commit went unnoticed. Hold the slot instead: emit the model name from the expired cache and draw a dimmed 'FB -' in the scoped position. No percentage, because a stale number a reader could mistake for live data is worse than no number at all. Fresh caches are untouched. Verified against a cache aged past hard expiry with a native payload on stdin: stock renders '5H .. WK .. SUB', patched renders '5H .. WK .. FB - SUB'.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What's wrong
On macOS the keychain can hold more than one item under the service name
Claude Code-credentials, told apart by account. Claude Code 2.1.278 leaves a legacyacct="claude-code-user"item whoseaccessTokenandrefreshTokenare empty strings, beside the liveacct="$USER"item it refreshes every session:security find-generic-password -s "Claude Code-credentials" -wnames only the service, so it returns whichever item comes back first. Where that is the stub, every/api/oauth/usagefetch authenticates with an empty bearer token and fails.Why nobody notices
The cache never lands,
_data_agepassesUSAGE_HARD_EXPIRY, and the native-payload branch stops enriching. The scoped per-model pill and the EXT indicator are then simply not drawn — while 5H and WK keep rendering from the stdinrate_limitsobject, which carries onlyfive_hourandseven_day.So the status line looks perfectly healthy. An omitted pill is indistinguishable from an account that has no scoped window at all. On the install where this was found, the fetch had been dead for four days.
The two commits
1. Pick the keychain item that actually holds a token. Probe the cached winner, then
$USER, thenclaude-code-user, then the bare lookup, and take the first item carrying a non-empty token. If every one comes back empty the layout has moved again, so enumerate the items under that service and try each — onedump-keychain, only on a layout change, because the winning account is cached in/tmp/pai-kc-acct-$USER. A fetch that returns nothing usable clears that cache so the next render re-probes rather than retrying a dead credential.2. Show a dead cache instead of hiding it. Past
USAGE_HARD_EXPIRY, emit the model name from the expired cache and hold the scoped slot with a dimmedFB -. No percentage: a stale number a reader could mistake for live data is worse than no number. Fresh caches are untouched.Verification
Keychain lookup, same machine:
Forced the fallback by running with a bogus
$USERand no cached hint, so every known account returned empty. It enumerated, found the live item, and rendered the pill.Rendering, native payload on stdin against a cache aged past hard expiry:
Linux is unaffected — that branch reads
~/.claude/.credentials.jsonand is untouched.bash -nclean.🤖 Generated with Claude Code
https://claude.ai/code/session_015V34cQV19jCGFpsEbFosL4