fix: gate ANTHROPIC_TOKEN cleanup to config version 8→9 migration

- Bump _config_version 8 → 9
- Move stale ANTHROPIC_TOKEN clearing into 'if current_ver < 9' block
  so it only runs once during the upgrade, not on every migrate_config()
- ANTHROPIC_TOKEN is still a valid auth path (OAuth flow), so we don't
  want to clear it repeatedly — only during the one-time migration from
  old setups that left it stale
- Add test_skips_on_version_9_or_later to verify one-time behavior
- All tests set config version 8 to trigger migration
This commit is contained in:
teknium1
2026-03-17 01:28:38 -07:00
parent b6a51c955e
commit e9f1a8e39b
2 changed files with 55 additions and 29 deletions

View File

@@ -349,7 +349,7 @@ DEFAULT_CONFIG = {
},
# Config schema version - bump this when adding new required fields
"_config_version": 8,
"_config_version": 9,
}
# =============================================================================
@@ -786,34 +786,6 @@ def migrate_config(interactive: bool = True, quiet: bool = False) -> Dict[str, A
except Exception:
pass # best-effort; don't block migration on sanitize failure
# ── Always: clear stale ANTHROPIC_TOKEN when better credentials exist ──
# Old setups left ANTHROPIC_TOKEN with an outdated value that shadows
# Claude Code auto-discovery (CLAUDE_CODE_OAUTH_TOKEN) or a direct
# ANTHROPIC_API_KEY.
try:
old_token = get_env_value("ANTHROPIC_TOKEN")
if old_token:
has_api_key = bool(get_env_value("ANTHROPIC_API_KEY"))
has_claude_code = False
try:
from agent.anthropic_adapter import (
read_claude_code_credentials,
is_claude_code_token_valid,
)
cc_creds = read_claude_code_credentials()
has_claude_code = bool(
cc_creds and is_claude_code_token_valid(cc_creds)
)
except Exception:
pass
if has_api_key or has_claude_code:
save_env_value("ANTHROPIC_TOKEN", "")
if not quiet:
source = "ANTHROPIC_API_KEY" if has_api_key else "Claude Code credentials"
print(f" ✓ Cleared stale ANTHROPIC_TOKEN (using {source} instead)")
except Exception:
pass
# Check config version
current_ver, latest_ver = check_config_version()
@@ -856,6 +828,32 @@ def migrate_config(interactive: bool = True, quiet: bool = False) -> Dict[str, A
tz_display = config["timezone"] or "(server-local)"
print(f" ✓ Added timezone to config.yaml: {tz_display}")
# ── Version 8 → 9: clear stale ANTHROPIC_TOKEN when better creds exist ──
if current_ver < 9:
try:
old_token = get_env_value("ANTHROPIC_TOKEN")
if old_token:
has_api_key = bool(get_env_value("ANTHROPIC_API_KEY"))
has_claude_code = False
try:
from agent.anthropic_adapter import (
read_claude_code_credentials,
is_claude_code_token_valid,
)
cc_creds = read_claude_code_credentials()
has_claude_code = bool(
cc_creds and is_claude_code_token_valid(cc_creds)
)
except Exception:
pass
if has_api_key or has_claude_code:
save_env_value("ANTHROPIC_TOKEN", "")
if not quiet:
source = "ANTHROPIC_API_KEY" if has_api_key else "Claude Code credentials"
print(f" ✓ Cleared stale ANTHROPIC_TOKEN (using {source} instead)")
except Exception:
pass
if current_ver < latest_ver and not quiet:
print(f"Config version: {current_ver}{latest_ver}")

View File

@@ -319,8 +319,16 @@ class TestSanitizeEnvLines:
class TestStaleAnthropicTokenMigration:
"""Test that migrate_config clears stale ANTHROPIC_TOKEN."""
def _write_config_version(self, tmp_path, version):
"""Write a config.yaml with a specific _config_version."""
config_path = tmp_path / "config.yaml"
import yaml
config = {"_config_version": version}
config_path.write_text(yaml.safe_dump(config, sort_keys=False))
def test_clears_stale_token_when_api_key_exists(self, tmp_path):
"""ANTHROPIC_TOKEN cleared when ANTHROPIC_API_KEY is also set."""
self._write_config_version(tmp_path, 8)
env_file = tmp_path / ".env"
env_file.write_text(
"ANTHROPIC_API_KEY=sk-ant-real-key\n"
@@ -339,6 +347,7 @@ class TestStaleAnthropicTokenMigration:
def test_clears_stale_token_when_claude_code_available(self, tmp_path):
"""ANTHROPIC_TOKEN cleared when Claude Code credentials exist."""
self._write_config_version(tmp_path, 8)
env_file = tmp_path / ".env"
env_file.write_text("ANTHROPIC_TOKEN=old-stale-token\n")
@@ -361,6 +370,7 @@ class TestStaleAnthropicTokenMigration:
def test_preserves_token_when_no_alternative(self, tmp_path):
"""ANTHROPIC_TOKEN kept when no API key or Claude Code creds exist."""
self._write_config_version(tmp_path, 8)
env_file = tmp_path / ".env"
env_file.write_text("ANTHROPIC_TOKEN=only-auth-method\n")
@@ -377,3 +387,21 @@ class TestStaleAnthropicTokenMigration:
env = load_env()
assert env.get("ANTHROPIC_TOKEN") == "only-auth-method"
def test_skips_on_version_9_or_later(self, tmp_path):
"""Migration doesn't fire when already at config version 9+."""
self._write_config_version(tmp_path, 9)
env_file = tmp_path / ".env"
env_file.write_text(
"ANTHROPIC_API_KEY=sk-ant-real-key\n"
"ANTHROPIC_TOKEN=should-stay\n"
)
with patch.dict(os.environ, {
"HERMES_HOME": str(tmp_path),
"ANTHROPIC_API_KEY": "sk-ant-real-key",
"ANTHROPIC_TOKEN": "should-stay",
}):
migrate_config(interactive=False, quiet=True)
env = load_env()
assert env.get("ANTHROPIC_TOKEN") == "should-stay"