v0.29.0
Syncs two commits from upstream realiti4/claude-swap through 3c3f2b8, plus the macOS test fixes that landed here after 0.28.0.
Fixed
- A custom
CLAUDE_CONFIG_DIRno longer reads another account's token. The active-credential read resolved its Keychain item from the fixed"Claude Code"service name, while the identity read one layer up honorsCLAUDE_CONFIG_DIR— so under a custom profile the store reported one account's identity against a different account's credential, silently, for every consumer of that read. It now resolves the item the way Claude Code does for the same environment (session.keychain_service_name, the derivation the delete, session-read and capture paths already share). This is not a corner case here:ccswap runlaunches Claude Code with a per-sessionCLAUDE_CONFIG_DIR, which is exactly the custom-profile shape. Thanks to @jamesvillarrubia upstream (#237). - The managed-key Keychain read is now default-profile-only. Same item, gated rather than redirected — there is no pinned derivation for Claude's managed-key service name under a custom profile, and capture already refuses it for that reason. A custom profile's own
primaryApiKeyis still read from its own config, so a managed key there is still found. - The test suite stopped leaking a temp directory per worker per run.
_ISOLATED_HOMEwas a module-levelmkdtemp()with no cleanup, reached once per process — so pytest-xdist made one per worker and nothing ever removed them (37,948 stray dirs measured on one upstream host). It is now allocated throughtmp_path_factory, inside pytest's basetemp, where pytest's own retention reclaims it. Thanks to @codeslake upstream (#267). - The three tests that only ever ran on Linux now run everywhere. Noted as a known failure in 0.28.0's release notes: each assumed the Linux shape of something the platform picks (a backup
.encthat macOS never writes because the bytes go to the Keychain; the XDG backup root that macOS does not use). Nothing was wrong with the code they cover — the tests now pin the backend they need and resolve the backup root throughpaths.get_backup_root()instead of naming a layout.
Notes
- No behavior change for a default-profile install: the resolved Keychain item is the same unsuffixed one, and an explicit
CLAUDE_CONFIG_DIRnaming the default profile tries the hashed item first and then falls back to the unsuffixed one. - The on-disk layout is unchanged and still uses upstream's
claude-swapdirectory names (~/.claude-swap-backup,$XDG_DATA_HOME/claude-swap), so existing installs need no migration. - Suite on macOS: 2146 passed, 3 skipped, 0 failed — the three macOS failures called out in 0.28.0 are gone.
Credits
Upstream work by @realiti4 and contributors.