Statusline Account Tier Refresh

Status:complete
Date:2026-08-02
Branch:fix/statusline-account-usage-refresh
Type:spec + implementation plan

Goal and acceptance criteria

When jacked switches the live Claude account, the installed statusline must show one coherent account identity. The email, organization, and Max/Pro tier badge must all describe the incoming account on the next render.

Acceptance criteria

  1. Given a primed statusline cache for an account with Max 5x, when the real credential-sync path switches to an account whose canonical tier is default_claude_max_20x, the next render contains the new email and Max 20x, and contains neither the old email nor Max 5x.
  2. If the incoming account has no known rate-limit tier, credential sync removes both outgoing tier fields so the statusline cannot attribute the previous account's badge to the new email.
  3. A personal target writes its canonical tier to userRateLimitTier; an organization-scoped target writes it to organizationRateLimitTier. Switching in either direction clears the other field first.
  4. Both explicit-tier and explicit-null updates preserve unrelated top-level and oauthAccount keys in every successfully parsed dictionary. A direct identity-only call that omits tier metadata also preserves existing tier fields for backward compatibility.
  5. Unique access-token and refresh-token canaries appear only in the credential stores. They never appear in .claude.json, the statusline account cache, or rendered output.
  6. Manual account selection, automatic account swapping, OAuth activation, and active-token resync all inherit the fix through the shared sync_credential_to_all_stores() boundary.
  7. The token-bearing credential files, platform keychain behavior, 5h/7d percentages, and statusline command registration remain unchanged.
  8. The statusline cache records the exact source revision it was derived from and is reused only while that revision still matches .claude.json. The revision includes a compact content digest from the same opened descriptor, so future-dated caches, old in-flight writers, coarse timestamps, and reused file identifiers cannot pin subsequent renders to the outgoing account.

Root cause and design

flowchart LR
  A[Manual or automatic switch] --> B[sync_credential_to_all_stores]
  B --> C[Credentials and keychain get target rateLimitTier]
  B --> D[update_claude_config_email]
  D --> E[Target email and org, outgoing tier remains]
  E --> F[statusline reparses .claude.json]
  F --> G[Mixed new email plus old Max badge]
  D -. fix .-> H[Explicitly replace or clear tier fields]
  H --> I[Source-versioned account cache]
  I --> J[Coherent next render]
  
Manual/auto switch -> shared credential sync -> atomic .claude.json identity and tier update -> coherent statusline render

The chosen fix keeps the existing four-argument update_claude_config_email() interface as an identity-only compatibility wrapper. A private immutable account-metadata object carries identity plus the tier into a one-argument atomic writer, satisfying the repository's argument-count guardrail without expanding the public API. Omitting tier metadata through the legacy wrapper preserves existing tier fields. An explicit value first removes both tier fields, then writes the canonical database tier to organizationRateLimitTier when organization_uuid is present or userRateLimitTier for a personal account. Passing an explicit null leaves both fields absent. The shared sync path always constructs the private metadata object with the account's rate_limit_tier, including null.

The renderer replaces cache-mtime ordering with a versioned JSON cache containing the rendered segment and a source signature derived from the open .claude.json descriptor (mtime nanoseconds, ctime nanoseconds, size, device, inode, and a compact BLAKE2 content digest). A cache hit requires an exact match against the current source signature. The digest prevents coarse timestamp or reused file-identifier collisions while retaining the substantially cheaper cached path that skips JSON parsing. Legacy one-line caches and malformed caches miss safely and are rewritten. If an in-flight render parses the outgoing file and writes after a switch, it tags that entry with the outgoing signature, so the following render rejects it.

Alternatives rejected

AlternativeWhy it is not selected
Delete only statusline-account.cache on switchThe cache is already invalidated by the atomic .claude.json rewrite. Re-reading the same mixed metadata cannot fix it.
Read .credentials.json from the rendererIt introduces a second source of account identity, makes a hot-path renderer parse token-bearing data, and leaves .claude.json internally inconsistent.
Patch manual and auto-swap callers separatelyIt duplicates policy and misses OAuth activation and token-resync callers. The shared writer is the correct consistency boundary.
Write the canonical tier to both Claude fieldsIt would misrepresent organization-derived metadata as a user override. Clearing the stale override and writing the organization field preserves field meaning.

Files and interfaces

FileResponsibility
jacked/api/credential_helpers.pyExtend the atomic ~/.claude.json metadata update and pass the incoming account's tier from the shared credential sync.
jacked/statusline.pyBind each cached account segment to the source config signature so stale or racing writers cannot survive a switch.
tests/unit/test_credential_sync.pyReproduce the reported cached Max 5x to Max 20x switch and cover explicit missing-tier clearing plus unspecified-tier preservation.
tests/unit/test_statusline.pyUpdate the cache-format contract and cover legacy migration, future timestamps, source-signature mismatch, and the in-flight stale-writer interleaving.

Tasks and verification

Task 1: Lock the regression with tests

Task 2: Implement the shared metadata invariant

Task 3: Bind the account cache to its source revision

Task 4: Prove behavior and review

Non-goals and negative requirements

Open questions

None blocking The account's organization UUID selects the semantically correct Claude field after both outgoing fields are cleared.


Generated from the jacked HTML artifact template for the reviewed implementation cycle.