fix(cli): only guard the default settings.json with the lite up and autoroute backups on login --config-claude

This commit is contained in:
mateo-berri 2026-09-09 18:19:56 -07:00
parent b554d79914
commit b3f2de058b
3 changed files with 40 additions and 7 deletions

View file

@ -41,9 +41,9 @@ from litellm.litellm_core_utils.cli_token_utils import (
)
from .claude_settings import (
SETTINGS_FILE_OWNERS,
ClaudeSettingsError,
claude_settings_path,
settings_file_owners,
write_claude_settings,
)
from .pkce_login import (
@ -782,7 +782,7 @@ def _configure_claude_code(base_url: str) -> None:
"""Point Claude Code at base_url by patching the settings.json it reads."""
settings_path: Final = claude_settings_path(os.environ)
try:
write_claude_settings(base_url, settings_path, SETTINGS_FILE_OWNERS)
write_claude_settings(base_url, settings_path, settings_file_owners(settings_path))
except ClaudeSettingsError as e:
raise click.ClickException(f"Logged in, but could not configure Claude Code: {e}")
click.echo(f"\nConfigured Claude Code: {settings_path} now routes through {base_url.rstrip('/')}.")

View file

@ -64,6 +64,11 @@ def claude_settings_path(environ: Mapping[str, str]) -> Path:
return Path(config_dir).expanduser() / "settings.json"
def settings_file_owners(settings_path: Path) -> tuple[SettingsFileOwner, ...]:
"""The commands whose backups guard settings_path: `lite up` and `lite autoroute up` only ever manage the default file."""
return SETTINGS_FILE_OWNERS if settings_path == CLAUDE_SETTINGS_PATH else ()
def load_json_or_empty(path: Path) -> dict[str, JsonValue]:
try:
content: Final = path.read_bytes() if path.exists() else b""
@ -197,5 +202,6 @@ __all__ = (
"load_json_or_empty",
"merge_claude_settings",
"resolve_api_key_helper",
"settings_file_owners",
"write_claude_settings",
)

View file

@ -1398,9 +1398,10 @@ class TestLoginConfigClaude:
def setup_method(self):
self.runner = CliRunner()
def _run_login(self, tmp_path, args, base_url="https://test.example.com"):
def _run_login(self, tmp_path, args, base_url="https://test.example.com", *, config_dir_env=None):
settings_path = tmp_path / "claude" / "settings.json"
backup_path = tmp_path / "claude_settings_backup.json"
env = {"CLAUDE_CONFIG_DIR": str(settings_path.parent)} if config_dir_env is None else config_dir_env
poll_response = Mock()
poll_response.status_code = 200
poll_response.json.return_value = {
@ -1417,17 +1418,19 @@ class TestLoginConfigClaude:
patch("litellm.proxy.client.cli.commands.auth.save_cli_token"),
patch("litellm.proxy.client.cli.interface.show_commands"),
patch(
"litellm.proxy.client.cli.commands.auth.SETTINGS_FILE_OWNERS",
"litellm.proxy.client.cli.commands.claude_settings.SETTINGS_FILE_OWNERS",
(SettingsFileOwner(backup_path, "lite up", "lite down"),),
),
patch(
"litellm.proxy.client.cli.commands.claude_settings.CLAUDE_SETTINGS_PATH",
tmp_path / "default-home" / ".claude" / "settings.json",
),
patch(
"litellm.proxy.client.cli.commands.claude_settings.shutil.which",
return_value="/usr/local/bin/lite",
),
):
result = self.runner.invoke(
login, args, obj={"base_url": base_url}, env={"CLAUDE_CONFIG_DIR": str(settings_path.parent)}
)
result = self.runner.invoke(login, args, obj={"base_url": base_url}, env=env)
return result, settings_path, backup_path
def test_default_login_does_not_touch_claude_settings(self, tmp_path):
@ -1460,6 +1463,30 @@ class TestLoginConfigClaude:
assert written["theme"] == "dark"
assert written["env"]["KEEP"] == "me"
def test_flag_refuses_while_lite_up_holds_the_default_settings_file(self, tmp_path):
default_settings_path = tmp_path / "default-home" / ".claude" / "settings.json"
(tmp_path / "claude_settings_backup.json").write_text("{}")
result, _settings_path, _backup_path = self._run_login(
tmp_path, ["--config-claude"], config_dir_env={"CLAUDE_CONFIG_DIR": ""}
)
assert result.exit_code != 0
assert "Login successful!" in result.output
assert "`lite up` is currently managing" in result.output
assert "Run `lite down` first" in result.output
assert not default_settings_path.exists()
def test_flag_writes_an_alternate_config_dir_even_while_lite_up_holds_the_default_file(self, tmp_path):
(tmp_path / "claude_settings_backup.json").write_text("{}")
result, settings_path, _backup_path = self._run_login(tmp_path, ["--config-claude"])
assert result.exit_code == 0, result.output
written = json.loads(settings_path.read_text())
assert written["apiKeyHelper"] == "/usr/local/bin/lite --base-url https://test.example.com auth print-token"
assert f"Configured Claude Code: {settings_path} now routes through https://test.example.com." in result.output
def test_settings_failure_is_reported_without_claiming_login_failed(self, tmp_path):
settings_path = tmp_path / "claude" / "settings.json"
settings_path.parent.mkdir(parents=True)