From cc3b41affc2746fdb24f27838a18af34e3d785bc Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 15 Jul 2026 15:05:44 -0700 Subject: [PATCH] fix(cli): surface a clean error on an empty or corrupt autoroute config A configure run killed between secure_create's O_TRUNC and the write completing leaves an empty config.yaml on disk. The next up read that via yaml.safe_load (None) into the generated-config TypeAdapter uncaught, surfacing a raw pydantic.ValidationError instead of pointing the user back at `lite autoroute configure`. --- .../proxy/client/cli/commands/autoroute/commands.py | 9 +++++++-- .../proxy/client/cli/autoroute/test_commands.py | 13 +++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/client/cli/commands/autoroute/commands.py b/litellm/proxy/client/cli/commands/autoroute/commands.py index acfebc0f370..75e0bac070a 100644 --- a/litellm/proxy/client/cli/commands/autoroute/commands.py +++ b/litellm/proxy/client/cli/commands/autoroute/commands.py @@ -7,7 +7,7 @@ from types import FrameType import click import yaml -from pydantic import JsonValue, TypeAdapter +from pydantic import JsonValue, TypeAdapter, ValidationError from ..up import CLAUDE_SETTINGS_PATH, load_json_or_empty, restore_claude_settings, write_backup from ..up import BackupRecord as ClaudeBackupRecord @@ -46,7 +46,12 @@ def _mint_and_embed_master_key() -> str: """ master_key = secrets.token_urlsafe(32) with open(CONFIG_PATH, "r") as f: - generated = _GENERATED_CONFIG_ADAPTER.validate_python(yaml.safe_load(f)) + try: + generated = _GENERATED_CONFIG_ADAPTER.validate_python(yaml.safe_load(f)) + except (yaml.YAMLError, ValidationError): + raise click.ClickException( + f"{CONFIG_PATH} is empty or corrupt. Run `lite autoroute configure` again to regenerate it." + ) general_settings = generated.get("general_settings") updated_settings: dict[str, JsonValue] = { **(general_settings if isinstance(general_settings, dict) else {}), diff --git a/tests/test_litellm/proxy/client/cli/autoroute/test_commands.py b/tests/test_litellm/proxy/client/cli/autoroute/test_commands.py index f9a520ff148..c32cb1e6dce 100644 --- a/tests/test_litellm/proxy/client/cli/autoroute/test_commands.py +++ b/tests/test_litellm/proxy/client/cli/autoroute/test_commands.py @@ -56,6 +56,19 @@ class TestUpCommand: assert result.exit_code != 0 assert "lite autoroute configure" in result.output + def test_surfaces_clean_error_on_empty_config_file(self, monkeypatch, tmp_path): + """A `configure` killed between secure_create's O_TRUNC and the write completing leaves an + empty config.yaml on disk -- yaml.safe_load(empty) returns None, and validating None as the + generated-config model raises a raw pydantic.ValidationError if uncaught.""" + config_path, _log_path, _settings_path, _backup_path, _pid_record_path = _patch_paths(monkeypatch, tmp_path) + config_path.write_text("") + + result = self.runner.invoke(up) + + assert result.exit_code != 0 + assert result.exception is None or isinstance(result.exception, SystemExit) + assert "lite autoroute configure" in result.output + def test_refuses_when_pid_record_exists_and_process_still_running(self, monkeypatch, tmp_path): config_path, _log_path, _settings_path, _backup_path, pid_record_path = _patch_paths(monkeypatch, tmp_path) config_path.write_text(yaml.safe_dump({"model_list": []}))