v0.32.0
A sync with upstream realiti4/claude-swap through 7187ce8, 33 commits. Most of it lands on session mode, where running an account as both the default login and a session could leave one copy of the token stale. That case is now handled instead of warned about.
Added
ccswap run 2 --require-session. Without it, asking to run an account that is already the default login quietly launches plainclaude. With it, that fast path refuses instead, so a script cannot think it got an isolated session when it got the default one.- The macOS menu bar can run as a launchd LaunchAgent.
ccswap menubarblocks the terminal that started it, so the status item died with the terminal.ccswap menubar --install-serviceregisters it to start at login, with--uninstall-serviceand--service-statusalongside. Logs go to~/Library/Logs/com.ccswap.menubar.{log,err}. loginExpiresAton JSON rows, when the stored login records when its refresh token expires. That is the moment the slot needs a fresh/loginandccswap add --slot N, so a script can warn days ahead instead of discoveringrelogin_requiredthe hard way.usageErrorandusageRetryAton rows whose usage is unavailable with nothing else to explain it.usageErrornames the last fetch failure by kind, such ashttp-429ortimeout, andusageRetryAtgives the next attempt while the cache is backing off.
Changed
- An exited session's credential is adopted into the account's backup before a switch or usage check reads that backup. A session refreshes its own copy of the token, so the backup went stale the moment the session rotated it.
ccswap switchrefuses to move the default login onto an account with a live session whose stored backup has already fallen behind. Activating a consumed generation could only fail withinvalid_granton its first refresh, so this is now an error rather than a warning you find out about later.- A live session's usage is read with the session's own credential and never refreshed. A read the server refuses shows as token expired and is not requested again until the session renews the credential itself.
- The token-persist warning goes to stderr. It ran inside
ccswap list --jsonand the other--jsoncommands, whose stdout is meant to be one machine-readable object.
Fixed
ccswap addrefuses a credential whose owner is not the account being added, instead of storing it under the wrong slot.- Session records whose PID has been recycled are ignored. A dead session's PID reassigned to an unrelated process read as a live session.
- An unreadable backup no longer produces a dead-token verdict. Not being able to read the file is not evidence the token is dead.
- A symlinked profile's Keychain entry is read under its target path.
- The menu bar stops leaking callbacks. Its rumps callback registry was never purged on rebuild, so memory grew without bound.
- The menu bar reports an interpreter that cannot draw the status item, gating on the interpreter build rather than its version, and requires macOS 26.
Fork-specific
- The LaunchAgent resolves the
ccswapconsole script. Upstream looks up its owncswap, which does not exist here, so both probes missed and the plist got a virtualenv-internal path thatccswap upgradethen breaks. - The launchd job is labelled
com.ccswap.menubar, not upstream'scom.cswap.menubar.