Merge pull request #42356 from BerriAI/litellm_cli_footer_version_guard

fix(cli): preserve newer installed status lines during setup
This commit is contained in:
tin-berri 2026-09-21 17:09:38 -07:00 • committed by GitHub
commit 24f616b637
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 200 additions and 10 deletions

View file

@ -22,8 +22,12 @@ from pathlib import Path
from types import MappingProxyType
from typing import Final, TypeAlias
import click
from filelock import FileLock
from packaging.version import InvalidVersion, Version
from pydantic import BaseModel, ConfigDict, JsonValue, TypeAdapter, ValidationError
from litellm._version import version as litellm_version
from litellm.litellm_core_utils.private_json import (
commit_staged_json,
discard_staged_json,
@ -75,6 +79,7 @@ BACKUP_PATH: Final = Path.home() / ".litellm" / "claude_settings_backup.json"
AUTOROUTE_BACKUP_PATH: Final = Path.home() / ".litellm" / "autorouter" / "claude_settings_backup.json"
CONFIGURE_STATE_PATH: Final = Path.home() / ".litellm" / "claude_configure_state.json"
STATUSLINE_SCRIPT_PATH: Final = Path.home() / ".litellm" / "statusline.py"
STATUSLINE_VERSION_PREFIX: Final = b"# litellm-statusline-version: "
@dataclass(frozen=True, slots=True)
@ -305,11 +310,54 @@ def statusline_command(script_path: Path, platform: str = sys.platform) -> str:
return " ".join(quote(token) for token in (sys.executable, str(script_path)))
def install_statusline_script(script_path: Path | None = None) -> str:
def _statusline_version(value: str) -> Version | None:
try:
return Version(value)
except InvalidVersion:
return None
def _installed_statusline_version(target: Path) -> Version | None:
try:
with target.open("rb") as script:
header: Final = script.readline(256)
except FileNotFoundError:
return None
if not header.startswith(STATUSLINE_VERSION_PREFIX):
return None
try:
return _statusline_version(header.removeprefix(STATUSLINE_VERSION_PREFIX).decode("ascii").strip())
except UnicodeDecodeError:
return None
def install_statusline_script(
script_path: Path | None = None,
*,
package_version: str = litellm_version,
write: Callable[[str, bytes], None] = write_private_bytes,
) -> str:
target: Final = script_path or STATUSLINE_SCRIPT_PATH
try:
ensure_private_dir(target.parent)
write_private_bytes(str(target), Path(statusline_script.__file__).read_bytes())
bundled_version: Final = _statusline_version(package_version)
with FileLock(str(target) + ".lock", timeout=10, mode=0o600):
installed_version: Final = _installed_statusline_version(target)
if installed_version is not None and (bundled_version is None or installed_version > bundled_version):
cli_version: Final = str(bundled_version) if bundled_version is not None else "unknown"
click.echo(
f"Keeping the status line from LiteLLM {installed_version}; this CLI is {cli_version}. "
"Upgrade the CLI to refresh it.",
err=True,
)
return statusline_command(target)
source: Final = Path(statusline_script.__file__).read_bytes()
header: Final = (
STATUSLINE_VERSION_PREFIX + str(bundled_version).encode("ascii") + b"\n"
if bundled_version is not None
else b""
)
write(str(target), header + source)
except OSError as e:
raise ClaudeSettingsError(f"Could not install the status line script at {target}: {e}") from e
return statusline_command(target)

View file

@ -1,7 +1,7 @@
"""Claude Code status line and Codex Stop hook for auto-routed sessions.
`lite` copies this file verbatim to ~/.litellm/statusline.py and registers it as Claude
Code's `statusLine` command and as Codex's `[[hooks.Stop]]` command, so it must stay
`lite` copies this file to ~/.litellm/statusline.py with a CLI version header when known and registers
it as Claude Code's `statusLine` command and as Codex's `[[hooks.Stop]]` command, so it must stay
standard-library only and must never import litellm. Claude Code re-runs it on every
status refresh (about every 300ms while typing), so the proxy is asked at most once per
TTL per session and every other refresh is served from a small on-disk cache that holds

View file

@ -15,12 +15,14 @@ dependencies = [
# When changing a floor, verify it installs + imports on every supported
# Python with: `uv pip install --resolution=lowest-direct .`
"fastuuid>=0.14.0,<1.0",
"filelock>=3.16.1,<4.0",
"httpx[http2]>=0.28.0,<1.0",
"openai>=2.20.0,<3.0.0",
"python-dotenv>=1.0.0,<2.0",
"tiktoken>=0.8.0,<1.0; python_version < '3.14'",
"tiktoken>=0.12.0,<1.0; python_version >= '3.14'",
"importlib-metadata>=8.0.0,<9.0",
"packaging>=24.0",
"tokenizers>=0.21.0,<1.0",
"click>=8.0.0,<9.0",
"jinja2>=3.1.6,<4.0",

View file

@ -5,13 +5,16 @@ import shlex
import stat
import sys
import time
from concurrent.futures import ThreadPoolExecutor, TimeoutError as FutureTimeoutError
from pathlib import Path
from threading import Event
from typing import Final
from unittest.mock import patch
import pytest
from click.testing import CliRunner
from litellm.litellm_core_utils.private_json import commit_staged_json
from litellm.litellm_core_utils.private_json import commit_staged_json, write_private_bytes
from litellm.proxy.client.cli.commands.claude_settings import (
ANTHROPIC_DEFAULT_MODEL_ENV_KEYS,
AUTOROUTE_BACKUP_PATH,
@ -773,7 +776,7 @@ class TestStatusLine:
script = tmp_path / "lite" / "statusline.py"
command = install_statusline_script(script)
assert script.read_bytes() == pathlib.Path(statusline_script.__file__).read_bytes()
assert script.read_bytes().split(b"\n", 1)[1] == pathlib.Path(statusline_script.__file__).read_bytes()
assert shlex.split(command) == [sys.executable, str(script)]
assert command == statusline_command(script)
assert stat.S_IMODE(script.stat().st_mode) == 0o600
@ -783,15 +786,13 @@ class TestStatusLine:
def test_a_reinstall_replaces_the_script_in_one_step_and_a_refused_one_leaves_the_old_script_whole(self, tmp_path):
# Claude Code may be running the script at the moment `lite` reinstalls it; the file it has open
# must stay complete, and a reinstall that cannot land must not leave a truncated script behind.
from litellm.proxy.client.cli.commands import statusline_script
script = tmp_path / "lite" / "statusline.py"
install_statusline_script(script)
bundled = pathlib.Path(statusline_script.__file__).read_bytes()
bundled = script.read_bytes()
with script.open("rb") as running:
install_statusline_script(script)
assert running.read() == bundled
assert [child.name for child in script.parent.iterdir()] == ["statusline.py"]
assert {child.name for child in script.parent.iterdir()} <= {"statusline.py", "statusline.py.lock"}
if os.geteuid() != 0:
script.parent.chmod(0o500)
@ -802,6 +803,141 @@ class TestStatusLine:
script.parent.chmod(0o700)
assert script.read_bytes() == bundled
@pytest.mark.parametrize(
("installed_version", "older_version"),
(("2.10.0", "2.9.0"), ("2.1.0", "2.1.0rc1"), ("2.1.0rc1", "2.1.0.dev2"), ("2.1.0.post1", "2.1.0")),
)
def test_an_older_cli_preserves_the_newer_footer(
self, tmp_path: Path, capsys: pytest.CaptureFixture[str], installed_version: str, older_version: str
) -> None:
script: Final = tmp_path / "statusline.py"
command: Final = install_statusline_script(script, package_version=installed_version)
installed: Final = script.read_bytes()
modified: Final = script.stat().st_mtime_ns
assert install_statusline_script(script, package_version=older_version) == command
assert script.read_bytes() == installed
assert script.stat().st_mtime_ns == modified
assert f"Keeping the status line from LiteLLM {installed_version}" in capsys.readouterr().err
@pytest.mark.parametrize(
"old_header", (b"", b"# litellm-statusline-version: invalid\n", b"# litellm-statusline-version: \xff\n")
)
def test_a_legacy_or_damaged_version_marker_is_repaired(self, tmp_path: Path, old_header: bytes) -> None:
from litellm.proxy.client.cli.commands import statusline_script
script: Final = tmp_path / "statusline.py"
script.write_bytes(old_header + b"print('old footer')\n")
install_statusline_script(script, package_version="2.1.0")
assert script.read_bytes() == (
b"# litellm-statusline-version: 2.1.0\n" + Path(statusline_script.__file__).read_bytes()
)
@pytest.mark.parametrize("next_version", ("2.1.0", "2.2.0"))
def test_an_equal_or_newer_cli_refreshes_the_footer(self, tmp_path: Path, next_version: str) -> None:
from litellm.proxy.client.cli.commands import statusline_script
script: Final = tmp_path / "statusline.py"
script.write_bytes(b"# litellm-statusline-version: 2.1.0\nprint('old footer')\n")
install_statusline_script(script, package_version=next_version)
assert script.read_bytes() == (
f"# litellm-statusline-version: {next_version}\n".encode() + Path(statusline_script.__file__).read_bytes()
)
def test_configure_keeps_a_newer_footer_while_updating_settings(
self, tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
script: Final = tmp_path / "statusline.py"
script.write_bytes(b"# litellm-statusline-version: 999999.0.0\nprint('newer footer')\n")
installed: Final = script.read_bytes()
rig: Final = _Rig(tmp_path, {"theme": "dark"})
rig.configure(script_path=script)
assert script.read_bytes() == installed
assert rig.read()["statusLine"]["command"] == statusline_command(script)
assert rig.read()["env"]["ANTHROPIC_BASE_URL"] == PROXY
assert "Keeping the status line" in capsys.readouterr().err
@pytest.mark.parametrize("package_version", ("unknown", "", "invalid-version"))
@pytest.mark.parametrize("existing", (None, b"print('legacy footer')\n", b"# litellm-statusline-version: invalid\n"))
def test_an_unknown_cli_version_can_install_and_refresh_an_unversioned_footer(
self, tmp_path: Path, package_version: str, existing: bytes | None
) -> None:
from litellm.proxy.client.cli.commands import statusline_script
script: Final = tmp_path / "statusline.py"
if existing is not None:
script.write_bytes(existing)
assert install_statusline_script(script, package_version=package_version) == statusline_command(script)
assert script.read_bytes() == Path(statusline_script.__file__).read_bytes()
def test_an_unknown_cli_version_preserves_a_versioned_footer(
self, tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
script: Final = tmp_path / "statusline.py"
command: Final = install_statusline_script(script, package_version="2.1.0")
installed: Final = script.read_bytes()
assert install_statusline_script(script, package_version="unknown") == command
assert script.read_bytes() == installed
assert "Keeping the status line from LiteLLM 2.1.0" in capsys.readouterr().err
@pytest.mark.parametrize(("first_version", "second_version"), (("2.0", "3.0"), ("3.0", "2.0")))
def test_overlapping_installs_keep_the_newest_footer(
self, tmp_path: Path, first_version: str, second_version: str
) -> None:
from litellm.proxy.client.cli.commands import statusline_script
script: Final = tmp_path / "statusline.py"
first_writing: Final = Event()
release_first: Final = Event()
second_started: Final = Event()
def paused_write(path: str, data: bytes) -> None:
first_writing.set()
assert release_first.wait(5), "First installer was never released"
write_private_bytes(path, data)
def second_install() -> str:
second_started.set()
return install_statusline_script(script, package_version=second_version)
with ThreadPoolExecutor(max_workers=2) as pool:
first: Final = pool.submit(install_statusline_script, script, package_version=first_version, write=paused_write)
try:
assert first_writing.wait(5), "First installer did not reach the write"
second: Final = pool.submit(second_install)
assert second_started.wait(5), "Second installer did not start"
with pytest.raises(FutureTimeoutError):
second.result(timeout=0.5)
finally:
release_first.set()
assert first.result(timeout=5) == statusline_command(script)
assert second.result(timeout=5) == statusline_command(script)
assert script.read_bytes() == b"# litellm-statusline-version: 3.0\n" + Path(statusline_script.__file__).read_bytes()
def test_a_failed_install_keeps_the_footer_and_releases_the_lock(self, tmp_path: Path) -> None:
script: Final = tmp_path / "statusline.py"
install_statusline_script(script, package_version="2.0")
installed: Final = script.read_bytes()
def failed_write(path: str, data: bytes) -> None:
raise OSError("disk full")
with pytest.raises(ClaudeSettingsError, match="disk full"):
install_statusline_script(script, package_version="3.0", write=failed_write)
assert script.read_bytes() == installed
assert install_statusline_script(script, package_version="3.0") == statusline_command(script)
assert script.read_bytes().startswith(b"# litellm-statusline-version: 3.0\n")
def test_configure_installs_it_and_unconfigure_removes_only_ours(self, tmp_path):
rig = _Rig(tmp_path, {"theme": "dark"})
script = tmp_path / "statusline.py"

4
uv.lock generated
View file

@ -4516,11 +4516,13 @@ dependencies = [
{ name = "boto3" },
{ name = "click" },
{ name = "fastuuid" },
{ name = "filelock" },
{ name = "httpx", extra = ["http2"] },
{ name = "importlib-metadata" },
{ name = "jinja2" },
{ name = "jsonschema" },
{ name = "openai" },
{ name = "packaging" },
{ name = "pydantic" },
{ name = "pydantic-settings" },
{ name = "python-dotenv" },
@ -4769,6 +4771,7 @@ requires-dist = [
{ name = "fastapi", marker = "extra == 'proxy'", specifier = ">=0.136.3,<1.0" },
{ name = "fastapi-sso", marker = "extra == 'proxy'", specifier = ">=0.19.0,<1.0" },
{ name = "fastuuid", specifier = ">=0.14.0,<1.0" },
{ name = "filelock", specifier = ">=3.16.1,<4.0" },
{ name = "google-cloud-aiplatform", marker = "extra == 'google'", specifier = ">=1.133.0,<2.0" },
{ name = "google-cloud-aiplatform", marker = "extra == 'proxy-runtime'", specifier = ">=1.133.0,<2.0" },
{ name = "google-cloud-iam", marker = "extra == 'extra-proxy'", specifier = ">=2.19.1,<3.0" },
@ -4802,6 +4805,7 @@ requires-dist = [
{ name = "numpydoc", marker = "extra == 'utils'", specifier = ">=1.8.0,<2.0" },
{ name = "nvidia-riva-client", marker = "extra == 'stt-nvidia-riva'", specifier = ">=2.15.0" },
{ name = "openai", specifier = ">=2.20.0,<3.0.0" },
{ name = "packaging", specifier = ">=24.0" },
{ name = "opentelemetry-api", marker = "extra == 'proxy-runtime'", specifier = "==1.28.0" },
{ name = "opentelemetry-exporter-otlp", marker = "extra == 'proxy-runtime'", specifier = "==1.28.0" },
{ name = "opentelemetry-instrumentation-fastapi", marker = "extra == 'proxy-runtime'", specifier = "==0.49b0" },