Skip to content

Commit 5503420

Browse files
Andymendez100claude
andcommitted
fix(rotation): always set CLAUDE_CONFIG_DIR explicitly, add session reset on rotate
- Remove ~/.claude special case in switch_account() — CLAUDE_CONFIG_DIR is now always set explicitly for all rotation accounts. Using the default ~/.claude dir caused both accounts to share the same rate limit pool when credentials resolved to the same org. - Each rotation account must use a dedicated config dir (e.g., ~/.claude-account1, ~/.claude-account2) with separate credentials. - Add reset_session("account_rotation") on successful rotation to prevent "No conversation found" errors from resuming account 1's session on account 2. - Update tests to reflect new always-set behavior (61/61 pass). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4e86bf4 commit 5503420

File tree

4 files changed

+23
-33
lines changed

4 files changed

+23
-33
lines changed

CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ The system uses a modular architecture with reusable components in the `lib/` di
9999
- `get_active_account()`, `get_total_accounts()`: state queries
100100
- `mark_account_rate_limited()`: records rate-limit timestamp per account
101101
- `get_next_available_account()`: finds next account not in 60-min cooldown, wraps around
102-
- `switch_account()`: sets the appropriate env var for the next account
102+
- `switch_account()`: sets `CLAUDE_CONFIG_DIR` explicitly for each account (never falls back to default `~/.claude`)
103+
- **Important**: Each rotation account must use a dedicated config dir (e.g., `~/.claude-account1`, `~/.claude-account2`). Do NOT use `~/.claude` — its keychain credentials conflict with interactive sessions and share the same rate limit pool.
103104
- `all_accounts_exhausted()`: checks if every account is in cooldown
104105
- `init_account_rotation()`: startup initialization, opt-in via `ACCOUNT_ROTATION=true`
105106
- `try_rotate_account()`: high-level function called from exit code 2 handler

lib/account_rotation.sh

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -232,14 +232,12 @@ switch_account() {
232232
local target_dir="${ACCOUNT_CONFIG_DIRS[$index]}"
233233
# Resolve ~ to $HOME (tilde doesn't expand inside quoted array values)
234234
target_dir="${target_dir/#\~/$HOME}"
235-
# The default ~/.claude dir requires CLAUDE_CONFIG_DIR to be UNSET.
236-
# Setting it explicitly to ~/.claude breaks auth (credentials are in the
237-
# system keychain, tied to the unset state). For any other dir, set it.
238-
if [[ "$target_dir" == "$HOME/.claude" || "$target_dir" == "$HOME/.claude/" ]]; then
239-
unset CLAUDE_CONFIG_DIR
240-
else
241-
export CLAUDE_CONFIG_DIR="$target_dir"
242-
fi
235+
# Always set CLAUDE_CONFIG_DIR explicitly for rotation accounts.
236+
# Each account must have its own dedicated config dir (e.g., ~/.claude-account1)
237+
# with separate credentials. Using the default ~/.claude is discouraged because
238+
# its keychain credentials are tied to the "unset" state and conflict with
239+
# interactive sessions.
240+
export CLAUDE_CONFIG_DIR="$target_dir"
243241
else
244242
return 1
245243
fi

ralph_loop.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1752,6 +1752,7 @@ main() {
17521752
local active_info
17531753
active_info=$(get_active_account)
17541754
log_status "SUCCESS" "🔄 Rotated to next account (active: $active_info)"
1755+
reset_session "account_rotation" # Sessions are per-account; start fresh
17551756
log_status "INFO" "Retrying with new account..."
17561757
update_status "$loop_count" "$(cat "$CALL_COUNT_FILE")" "account_rotated" "running"
17571758
sleep 3

tests/unit/test_account_rotation.bats

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -764,27 +764,23 @@ EOF
764764
}
765765

766766
# ============================================================================
767-
# Default ~/.claude config dir handling
767+
# Explicit config dir handling (always set CLAUDE_CONFIG_DIR)
768768
# ============================================================================
769769

770-
@test "switch_account: unsets CLAUDE_CONFIG_DIR for default ~/.claude dir" {
771-
# The default ~/.claude dir requires CLAUDE_CONFIG_DIR to be UNSET.
772-
# Setting it explicitly breaks auth (credentials tied to unset state).
773-
export ACCOUNT_CONFIG_DIRS=("$HOME/.claude" "/tmp/test-claude-account2")
770+
@test "switch_account: always sets CLAUDE_CONFIG_DIR explicitly for config_dir accounts" {
771+
# Each rotation account must have a dedicated config dir.
772+
# CLAUDE_CONFIG_DIR is always set explicitly — no unset/fallback to default.
773+
export ACCOUNT_CONFIG_DIRS=("/tmp/test-claude-account1" "/tmp/test-claude-account2")
774774
export ACCOUNT_KEYS=()
775775
_ensure_state_file
776776

777-
# Set CLAUDE_CONFIG_DIR to something first to prove it gets unset
778-
export CLAUDE_CONFIG_DIR="/tmp/something"
779-
780777
switch_account 0 "config_dir"
781778

782-
# Should be unset, not set to ~/.claude
783-
[[ -z "${CLAUDE_CONFIG_DIR:-}" ]]
779+
[[ "$CLAUDE_CONFIG_DIR" == "/tmp/test-claude-account1" ]]
784780
}
785781

786-
@test "switch_account: sets CLAUDE_CONFIG_DIR for non-default dirs" {
787-
export ACCOUNT_CONFIG_DIRS=("$HOME/.claude" "/tmp/test-claude-account2")
782+
@test "switch_account: sets CLAUDE_CONFIG_DIR for second account" {
783+
export ACCOUNT_CONFIG_DIRS=("/tmp/test-claude-account1" "/tmp/test-claude-account2")
788784
export ACCOUNT_KEYS=()
789785
_ensure_state_file
790786

@@ -793,16 +789,14 @@ EOF
793789
[[ "$CLAUDE_CONFIG_DIR" == "/tmp/test-claude-account2" ]]
794790
}
795791

796-
@test "switch_account: handles ~/.claude/ with trailing slash" {
797-
export ACCOUNT_CONFIG_DIRS=("$HOME/.claude/" "/tmp/test-claude-account2")
792+
@test "switch_account: resolves tilde in config dir paths" {
793+
export ACCOUNT_CONFIG_DIRS=("~/custom-claude-dir" "/tmp/test-claude-account2")
798794
export ACCOUNT_KEYS=()
799795
_ensure_state_file
800796

801-
export CLAUDE_CONFIG_DIR="/tmp/something"
802-
803797
switch_account 0 "config_dir"
804798

805-
[[ -z "${CLAUDE_CONFIG_DIR:-}" ]]
799+
[[ "$CLAUDE_CONFIG_DIR" == "$HOME/custom-claude-dir" ]]
806800
}
807801

808802
@test "switch_account: unsets CLAUDE_CONFIG_DIR when switching to key type" {
@@ -830,19 +824,15 @@ EOF
830824
[[ -z "${ANTHROPIC_API_KEY:-}" ]]
831825
}
832826

833-
@test "switch_account: resolves tilde in config dir paths" {
827+
@test "switch_account: resolves tilde in all config dir paths" {
834828
# Tilde doesn't expand inside quoted array values in .ralphrc
835-
export ACCOUNT_CONFIG_DIRS=("~/.claude" "~/.claude-account2")
829+
export ACCOUNT_CONFIG_DIRS=("~/.claude-account1" "~/.claude-account2")
836830
export ACCOUNT_KEYS=()
837831
_ensure_state_file
838832

839-
export CLAUDE_CONFIG_DIR="/tmp/something"
840-
841-
# ~/.claude should resolve to $HOME/.claude and trigger the unset path
842833
switch_account 0 "config_dir"
843-
[[ -z "${CLAUDE_CONFIG_DIR:-}" ]]
834+
[[ "$CLAUDE_CONFIG_DIR" == "$HOME/.claude-account1" ]]
844835

845-
# ~/.claude-account2 should resolve to $HOME/.claude-account2
846836
switch_account 1 "config_dir"
847837
[[ "$CLAUDE_CONFIG_DIR" == "$HOME/.claude-account2" ]]
848838
}

0 commit comments

Comments
 (0)