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.
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.userRateLimitTier; an organization-scoped target writes it to organizationRateLimitTier. Switching in either direction clears the other field first.oauthAccount keys in every successfully parsed dictionary. A direct identity-only call that omits tier metadata also preserves existing tier fields for backward compatibility..claude.json, the statusline account cache, or rendered output.sync_credential_to_all_stores() boundary..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.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.
| Alternative | Why it is not selected |
|---|---|
Delete only statusline-account.cache on switch | The cache is already invalidated by the atomic .claude.json rewrite. Re-reading the same mixed metadata cannot fix it. |
Read .credentials.json from the renderer | It 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 separately | It 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 fields | It would misrepresent organization-derived metadata as a user override. Clearing the stale override and writing the organization field preserves field meaning. |
| File | Responsibility |
|---|---|
jacked/api/credential_helpers.py | Extend the atomic ~/.claude.json metadata update and pass the incoming account's tier from the shared credential sync. |
jacked/statusline.py | Bind each cached account segment to the source config signature so stale or racing writers cannot survive a switch. |
tests/unit/test_credential_sync.py | Reproduce the reported cached Max 5x to Max 20x switch and cover explicit missing-tier clearing plus unspecified-tier preservation. |
tests/unit/test_statusline.py | Update the cache-format contract and cover legacy migration, future timestamps, source-signature mismatch, and the in-flight stale-writer interleaving. |
.claude.json, the statusline cache, or rendered output.oauthAccount object as email and organization.account.get("rate_limit_tier") from sync_credential_to_all_stores().fstat() from the same descriptor used to parse account metadata.uv run python -m pytest tests/unit/test_credential_sync.py tests/unit/test_statusline.py.uv run ruff check jacked/api/credential_helpers.py jacked/statusline.py tests/unit/test_credential_sync.py tests/unit/test_statusline.py.uv run python -m pytest.organization_uuid selects the compatible Claude metadata field, while tests lock the effective display invariant.~/.claude.json keys or follow a symlink at that path.~/.claude.json; that separate behavior is not exercised by the reported mixed-account state.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.