Credential storage and safety

Where credentials live per platform and the rules every swap enforces

All state lives in ~/.config/tokenmaxxing/. Credential I/O goes through a single platform-selected store facade; call sites never branch on platform.

macOS: credentials live in the login keychain as generic-password items, matching where Claude Code itself keeps them. The live item is the one claude reads; each pooled account's backup is parked as its own tokenmaxxing-cred-<uuid8> keychain item. Nothing is plaintext on disk. Reads use security find-generic-password -w (the secret only ever appears on stdout); writes pipe an add-generic-password line into security -i over stdin so the secret never appears in any process's argv. The interactive mode has a roughly 4 KB line buffer, so oversized blobs fall back to an argv write - one reason parked items store only the minimal OAuth object.

Linux: Claude Code stores credentials as a 0600 plaintext file, and tokenmaxxing follows the same model: the live file is claude's own .credentials.json, and parked backups are atomic 0600 files under ~/.config/tokenmaxxing/creds/ in a 0700 directory. Atomic here means write-to-temp, fsync, then rename: a concurrent reader always sees a whole old or whole new file, never a partial one.

The safety rules the swap enforces:

  • Harvest by true owner. Before a swap, the live credential's actual owner is resolved by asking the API which organization the token belongs to (fetchTokenOrg against the OAuth roles endpoint) - never by trusting the stored active label, which can drift. A drifted label once caused one account's harvest to overwrite another account's only backup. If the live credential belongs to an account outside the pool, the swap refuses outright rather than clobber it.
  • One critical section. The harvest, the install of the new credential, the backup of its rotated token, the ~/.claude.json account swap, and the active-label commit all happen inside the same locked section.
  • Interlock with claude's own refresh. A near-expiry session can rotate its own token at any moment, so credential writes also take Claude Code's refresh lock (best-effort, with a stale-lock reclaim and a bounded timeout).
  • Dead grants degrade cleanly. A refresh that comes back invalid_grant flags that account as needs-reauth and persists the flag before the error propagates, so retry loops shrink the candidate set and terminate. tokenmaxxing auth fixes a flagged account in place, and it refuses a login that lands on a different account than the one being repaired.
  • No token logging. Tokens never appear in logs, errors, or output. Error bodies from token and usage endpoints are never surfaced raw; only an allowlisted set of fields survives, because a token endpoint's failure body can echo request material.
  • Probe hygiene. Any spawned claude probe scrubs the eight environment variables claude honors before its keychain lookup (ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, CLAUDE_SECURESTORAGE_CONFIG_DIR, and so on), so a probe can never be attributed to the wrong account.