I run about fifteen Claude Code agents — separate sessions, each logged into its own account so they don’t all burn through the same rate limit (think of each account as its own monthly phone-data plan; when one runs dry, that agent stalls). To juggle them I wrote a little bash tool: claude-account save <name> snapshots whatever account is currently live into a named credential file, and claude-account use <name> swaps it back in later.
It worked for weeks. Then I noticed an agent acting like it was out of budget when I knew that account was fresh.
Here’s what had actually happened, and why it took me a while to see. My save command was dumb in the worst way: it wrote the live token to the target filename, no questions asked. So one evening I ran save work-b while account A was still the live one. It cheerfully stamped account A’s token into work-b’s file. Now two named accounts pointed at the same login. When work-b later hit its limit, switching to it did nothing — because it wasn’t work-b anymore.
The nasty part is that this is invisible. A clobbered credential doesn’t throw an error. It looks exactly like a normal rate-limit failure: the agent switches, still gets walled, you shrug and assume that account was tired too. I spent an embarrassing amount of time debugging the symptom — checking limits, re-logging in — when the real damage was a silent overwrite that happened minutes earlier.
And it was about to get much worse. I’d just added sync --fleet, which pushes credential files out to all the agent machines. A duplicated token wouldn’t have stayed one mistake — it would have been broadcast to the whole fleet. One bad save, fifteen agents sharing a login.
My first instinct was to add an “are you sure?” prompt. I’m glad I didn’t. A prompt protects me when I’m paying attention, which is exactly when I don’t need protecting. The failure happens when I’m tired or scripting it. The check has to live in the code path, not in my attention.
So I made it structural. Every token gets identified by the sha256 of its accessToken — never by its filename, never by what the caller claims it is.
The four guards in claude-account save / sync give me the detail
The rule: hash the token, compare hashes, decide before writing.
live_hash=$(sha256_of "$LIVE_TOKEN")
target_hash=$(sha256_of_file "$CRED_DIR/$name.json")
# 1. no-op: live token already matches the target
[ "$live_hash" = "$target_hash" ] && { echo "already current"; exit 0; }
# 2. cross-account clobber: this exact token is already saved under a DIFFERENT name
for f in "$CRED_DIR"/*.json; do
[ "$(sha256_of_file "$f")" = "$live_hash" ] && [ "$f" != "$CRED_DIR/$name.json" ] \
&& { echo "refusing: live token belongs to $(basename "$f" .json)"; exit 1; }
done
# 3. overwriting a different stored token needs explicit --force
[ -n "$target_hash" ] && [ "$target_hash" != "$live_hash" ] && [ -z "$FORCE" ] \
&& { echo "refusing: $name holds a different token (use --force)"; exit 1; }
# 4. sync pre-flight: refuse to push if any two files share an accessToken
sort <(for f in "$CRED_DIR"/*.json; do sha256_of_file "$f"; done) | uniq -d | grep -q . \
&& { echo "duplicate token across credential files — aborting fleet sync"; exit 1; }Guard 2 is the one that caught my actual bug. Guard 4 is the one that would have saved the fleet.
The general lesson I took: when a tool writes credentials, verify identity by content — hash the token and compare — never trust the filename or the caller’s intent. A filename is a label someone typed; the hash is what the token actually is. If you rotate or sync logins across a fleet, put that check before the write. The clobber you can debug later is the one you’ll never notice; the clobber you refuse up front never ships.