Skip to content

feat(cli): add lite autoroute to QA complexity-based auto-routing against a real proxy - #33249

Merged
krrish-berri-2 merged 12 commits into
litellm_lite_up_downfrom
litellm_autoroute_qa
Jul 15, 2026
Merged

feat(cli): add lite autoroute to QA complexity-based auto-routing against a real proxy#33249
krrish-berri-2 merged 12 commits into
litellm_lite_up_downfrom
litellm_autoroute_qa

Conversation

@krrish-berri-2

@krrish-berri-2 krrish-berri-2 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

This depends on #33231 (litellm_lite_up_down), which is why it's based on that branch instead of litellm_internal_staging directly; it reuses that PR's settings-backup/restore helpers.

Try it yourself

Install the CLI straight from this PR's commit, no PyPI release needed (swap in whatever this branch's latest commit is by the time you read this):

curl -fsSL https://raw.githubusercontent.com/BerriAI/litellm/851b38fc4a7f80fd9b8c82daa6275a561a30fe84/scripts/install-cli.sh | \
  LITELLM_CLI_REF=851b38fc4a7f80fd9b8c82daa6275a561a30fe84 sh

Start your real proxy (or point at one you already run):

python litellm/proxy/proxy_cli.py --config litellm/proxy/dev_config.yaml --detailed_debug --reload

Point the CLI at your proxy and key, and see what models it can reach:

export LITELLM_PROXY_URL=http://localhost:4000
export LITELLM_PROXY_API_KEY=sk-...
lite model-groups list

Assign models to the four complexity tiers with the fuzzy picker (type to filter, tab to multi-select, enter to confirm):

lite autoroute configure

Launch the ephemeral auto-router proxy. This also points Claude Code at it, including its Sonnet/Haiku/Opus defaults, so no /model juggling is needed:

lite autoroute up

In another terminal, use Claude Code as normal:

claude

Send a trivial one-line question, then a long multi-step request, and watch the lite autoroute up terminal: each one gets classified and routed to a different tier's model, forwarded to your real proxy, which shows the same calls in its own logs (http://localhost:4000/ui/?page=logs).

Press Ctrl-C on lite autoroute up when done; it restores your original Claude Code settings. If it ever dies uncleanly instead (kill -9, a crash), recover with:

lite autoroute down

What I verified

Ran this against two live, unmodified litellm proxy processes with real Anthropic/OpenAI keys, no mocks: a request to the ephemeral proxy's autorouter model got classified by auto_router/complexity_router, routed to the SIMPLE tier's model, forwarded over HTTP to the real proxy, which called Anthropic's real API and returned a genuine completion, all traceable in both proxies' logs. Also confirmed configure's generated config has the right deployments, up patches and restores ~/.claude/settings.json correctly on both SIGINT and kill -9, and a second up refuses to start while one is already running.

That pass caught two real bugs, both fixed here: the ephemeral proxy's freshly-minted key was written to litellm_settings.master_key, which the proxy server never reads, so it had no real authentication; and model discovery crashed on a real proxy's /model_group/info response whenever a model had an explicit "mode": null, which is common for embedding models registered without a mode.

Type

🆕 New Feature
✅ Test
📖 Documentation

Changes

Adds lite model-groups list, a thin wrapper over ModelGroupsManagementClient.info() (/model_group/info), and lite autoroute configure/up/down under litellm/proxy/client/cli/commands/autoroute/. configure discovers the models a key can access, walks through assigning them to SIMPLE/MEDIUM/COMPLEX/REASONING tiers via a fuzzy type-to-filter picker, with optional LLM-classifier, semantic-matching, and adaptive selection, and writes a generated config.yaml where every referenced model is its own litellm_proxy/<name> deployment forwarding back to the real proxy with the real key. up launches that config as an ephemeral local proxy with a freshly minted key, patches Claude Code's settings (including its default Sonnet/Haiku/Opus models, since Router resolves auto-router deployments by literal model name with no wildcard support) to point at it, streams its routing-decision logs live, and restores everything on Ctrl-C/SIGTERM; down is the manual recovery path after an unclean exit.

up.py's write_backup/read_backup/restore_claude_settings are generalized to take explicit paths (defaulting to the existing constants, so lite up/lite down are unaffected) so this feature reuses them instead of duplicating the backup/restore logic.

scripts/install-cli.sh gains an opt-in LITELLM_CLI_REF override so a branch (or tag, or commit) can be installed straight from source instead of the latest PyPI release; unset, it installs litellm[cli] from PyPI exactly as before.

QA runbook

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

…inst a real proxy

Lets a customer try litellm's complexity_router against models they already
have on their existing, unmodified production proxy, with no config.yaml
edits and no new infra. lite autoroute configure discovers accessible
models via /model_group/info and walks through tier assignment (plus
optional LLM classifier / semantic matching / adaptive selection); every
referenced model becomes its own litellm_proxy/<name> deployment forwarding
back to the real proxy with the real key, so every actual call, routed
completions, classifier calls, embedding calls, still lands on their real
proxy. lite autoroute up launches that generated config as an ephemeral
local proxy, patches ~/.claude/settings.json to point Claude Code at it, and
streams routing decisions live; Ctrl-C/SIGTERM (or lite autoroute down
after an unclean exit) restores everything.

Also adds lite model-groups list (a thin CLI wrapper over the existing
ModelGroupsManagementClient), and generalizes up.py's settings-backup/restore
helpers to take explicit paths so this feature can reuse them instead of
duplicating the logic.

Depends on litellm_lite_up_down (#33231) for that generalization.
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 60d3d05704

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread litellm/proxy/client/cli/commands/autoroute/settings.py Outdated
Comment thread litellm/proxy/client/cli/commands/autoroute/commands.py
Comment thread litellm/proxy/client/cli/commands/autoroute/commands.py
@greptile-apps

greptile-apps Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds lite autoroute configure/up/down — a developer-facing CLI workflow that discovers accessible model groups on a real LiteLLM proxy, lets the user assign them to complexity tiers via a fuzzy picker, generates an ephemeral forwarding proxy config, launches it locally, and hot-patches Claude Code's settings to route through it for the session duration. It also adds lite model-groups list as a standalone discovery command and generalises write_backup/restore_claude_settings in up.py to accept explicit paths so the autoroute feature can reuse the backup/restore logic without duplication.

  • autoroute/commands.py: orchestrates up/down; guards against stale backups, live duplicate processes, and empty/corrupt configs; _mint_and_embed_master_key correctly targets general_settings.master_key (not litellm_settings).
  • autoroute/process.py: subprocess lifecycle (port allocation, health polling, SIGTERM→SIGKILL teardown, log streaming); launch_proxy binds the ephemeral proxy to 127.0.0.1 to prevent unintended LAN exposure.
  • autoroute/settings.py + autoroute/config.py + autoroute/wizard.py: config model, YAML generation, and interactive tier-assignment wizard with InquirerPy fuzzy prompts.

Confidence Score: 5/5

Safe to merge; no blocking defects found in the changed code paths.

Previously flagged issues (stale backup overwrite, uncaught ValidationError on empty config, orphaned subprocess after health-check timeout) are all addressed in the final state. The remaining findings are both non-blocking quality suggestions: the proxy log file is created with default permissions rather than 0600, and allocate_free_port has a small TOCTOU window between releasing the socket and the proxy binding to it. Neither causes incorrect behavior or a security breach in practice for a localhost developer tool.

process.py — log file permissions and port allocation.

Important Files Changed

Filename Overview
litellm/proxy/client/cli/commands/autoroute/commands.py Core up/down/configure command orchestration. Stale-backup guard, try/except around YAML load, and orphaned-process cleanup are all addressed. _mint_and_embed_master_key correctly targets general_settings.master_key.
litellm/proxy/client/cli/commands/autoroute/process.py Process lifecycle helpers. Two issues: log file created with default permissions (potential info leak) and TOCTOU race between port allocation and proxy bind. Both are P2.
litellm/proxy/client/cli/commands/autoroute/config.py Config model and generation logic. Handles mode: null from real proxies, deduplicates model deployments, correctly places master_key under general_settings. Clean.
litellm/proxy/client/cli/commands/autoroute/wizard.py Configure wizard using InquirerPy fuzzy picker. Properly guards non-list API responses with ClickException, checks interactivity upfront, and writes config with secure_create (0600).
litellm/proxy/client/cli/commands/autoroute/settings.py Claude Code settings patching. Clears apiKeyHelper, sets ANTHROPIC_AUTH_TOKEN + BASE_URL, forces all default model tier env vars to 'autorouter'. Correct and well-tested.
litellm/proxy/client/cli/commands/up.py Refactored write_backup/read_backup/restore_claude_settings to accept explicit paths; added secure_create context manager; load_json_or_empty now handles empty files. Non-breaking for existing lite up/lite down.
litellm/proxy/client/cli/commands/model_groups.py Thin lite model-groups list command. Guards non-list response with ClickException (replacing the previous bare assert). Table and JSON output modes work correctly.
scripts/install-cli.sh Adds LITELLM_CLI_REF opt-in for source installs. Unset defaults to PyPI release unchanged. The variable is properly double-quoted when passed to uv, preventing word-splitting issues.
tests/test_litellm/proxy/client/cli/autoroute/test_commands.py Good coverage: refuses on missing config, live PID, stale backup, health-check failure; happy path validates settings patching, permissions, backup/restore lifecycle, and master-key embedding.
tests/test_litellm/proxy/client/cli/autoroute/test_wizard.py Tests both the wizard orchestration (mocking the picker) and the real InquirerPy widget via prompt_toolkit pipe input. Timing-based async tests could be fragile on very slow CI but are reasonable for widget testing.

Reviews (7): Last reviewed commit: "fix(cli): bind the ephemeral autoroute p..." | Re-trigger Greptile

Comment thread litellm/proxy/client/cli/commands/autoroute/wizard.py
Comment thread litellm/proxy/client/cli/commands/model_groups.py
Comment thread litellm/proxy/client/cli/commands/autoroute/process.py Outdated
Comment thread litellm/proxy/client/cli/commands/autoroute/config.py
@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.47319% with 28 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...llm/proxy/client/cli/commands/autoroute/process.py 77.89% 21 Missing ⚠️
...lm/proxy/client/cli/commands/autoroute/commands.py 95.74% 4 Missing ⚠️
...ellm/proxy/client/cli/commands/autoroute/wizard.py 95.31% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

Comment thread litellm/proxy/client/cli/commands/autoroute/commands.py Outdated
def _teardown() -> None:
if not restored.acquire(blocking=False):
return
terminate(process.pid)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium: Released proxy port can be impersonated

A Claude process that started during up retains the loopback URL after teardown. Once this process is terminated, another local account can bind the released port and receive subsequent prompts and the authorization token from that still-running Claude session. The endpoint needs a lifecycle that remains bound until its clients exit, such as launching Claude as a managed child or retaining a non-forwarding guard listener; restoring the settings file alone does not update existing processes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepting this as a known, documented tradeoff rather than fixing in code for this PR: a real fix (e.g. a guard listener that keeps the port bound and inert until lite autoroute up/down explicitly reclaims it) is a meaningfully larger change than this PR's scope, and the underlying risk (a Claude Code session that outlives up keeps sending to a now-unbound port) is the same one-time-patch tradeoff lite up already ships with, just with a static token instead of a re-resolved one. This is now called out explicitly in the README's Caveats section and in the up/teardown CLI output itself, so users see it at the point of risk. Leaving this open rather than silently dismissing it.

@veria-ai

veria-ai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

PR overview

This pull request adds a CLI “lite autoroute” workflow for QA of complexity-based routing through a real LiteLLM proxy. The touched autoroute command code manages starting, configuring, and tearing down local proxy-backed Claude sessions for that flow.

There is one open security concern remaining after three prior issues were addressed. The remaining issue is a local lifecycle problem: after teardown, a still-running client can keep using a loopback endpoint whose port may be rebound by another local user, exposing subsequent prompts and an authorization token. The risk is meaningful on shared machines but depends on local access and the teardown/client timing, so the blast radius is limited.

Open issues (1)

Fixed/addressed: 3 · PR risk: 6/10

complexity_router already supports a pool of models per tier (randomly
picked per request; adaptive mode specifically needs a pool to choose
within), but the configure wizard only ever let you assign one. Tiers are
now a tuple of model names; the wizard prompt accepts comma-separated
indices to pick more than one per tier.
Numbered-index selection didn't scale past a handful of models, so switch
the tier picker to InquirerPy's fzf-style fuzzy search. Also set
ANTHROPIC_DEFAULT_{SONNET,HAIKU,OPUS}_MODEL to "autorouter" in Claude
Code's settings, since Router resolves auto-router deployments by literal
model name with no wildcard support, so a "*" catch-all model_name would
never match real traffic.
Lets testers try an unreleased branch's CLI changes with the same
curl-piped installer, instead of waiting for a PyPI release.
Clears osv-scan findings for PYSEC-2026-3444 and PYSEC-2026-3447.
commands.py wrote config.yaml (embeds the real proxy key) and Claude
Code's settings.json (embeds the ephemeral proxy's master key) with
plain open(), landing at the umask-derived default (commonly 0644)
until a later chmod call caught up. That window, and the missed case
where settings.json already exists (chmod never ran at all there),
left a credential-bearing file readable by another local account.

secure_create() fixes the mode via fchmod on the fd before any
content is written, covering both the brand-new-file and
already-exists cases, and commands.py/wizard.py now route their
sensitive writes through it.
Comment thread litellm/proxy/client/cli/commands/up.py Outdated
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptile review

…ed port

lite autoroute up's master key is embedded statically (unlike lite up's
apiKeyHelper, resolved per request), so a Claude Code session still
running after teardown keeps sending it, along with prompt content, to
a now-unbound loopback port that another local account can bind. This
is the same one-time-patch tradeoff lite up already accepts, just with
a static secret instead of a re-resolved one -- document it in the
README's Caveats section and surface it in the teardown message itself.
- terminate the ephemeral proxy child process when its health check
  fails, instead of leaking an orphaned, unrecoverable process bound
  to the port
- replace bare assert isinstance checks (no-ops under python -O) with
  click.ClickException in the model-groups list and configure wizard
  code paths
- close launch_proxy's log file handle once the child process has
  inherited its fd, instead of leaking it
- add build_generated_proxy_config to config.py's __all__
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptile review

Comment thread litellm/proxy/client/cli/commands/autoroute/commands.py
write_backup wrote the backup (which can embed the original
apiKeyHelper/settings content) with plain open() + a chmod call after
the fact -- the same permissive-until-corrected window already fixed
for autoroute's config.yaml and Claude settings writes, and missed
entirely when the backup file already exists with broader permissions.

Moves secure_create (atomic-enough 0600 via fchmod before any content
is written) to up.py, the module both lite up and lite autoroute
share, and has autoroute/process.py import it from there instead of
keeping its own copy.
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptile review

Comment thread scripts/install-cli.sh
LITELLM_PACKAGE="litellm[cli]"
# Defaults to the PyPI release; LITELLM_CLI_REF opts into installing from source instead.
if [ -n "${LITELLM_CLI_REF:-}" ]; then
LITELLM_PACKAGE="litellm[cli] @ git+https://github.com/BerriAI/litellm.git@${LITELLM_CLI_REF}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this a security issue, why are we now installing from git vs using the package ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a security issue, in my opinion, as user is specifying the version they want to install.

the goal is to allow a user to test the autorouter without waiting on a litellm release (which can take longer)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you want i can have it be a notice to them on startup or on the doc - that this is an experimental, fast method

Comment thread litellm/proxy/client/cli/commands/autoroute/process.py Outdated
The pid-record check only catches a still-live duplicate process; a
SIGKILL'd `up` leaves no live pid but does leave AUTOROUTE_BACKUP_PATH
behind. Without this guard, a fresh `up` overwrote that backup with
the currently-patched Claude settings instead of the true originals,
so `down`/Ctrl-C would restore the wrong content permanently. up.py's
`lite up` already guards the analogous case; mirror it here.
proxy_cli.py defaults --host to 0.0.0.0 when not passed explicitly.
launch_proxy never passed it, so the ephemeral proxy -- despite every
base_url in this module being built from 127.0.0.1 -- was actually
reachable from other hosts on the network, including its
unauthenticated-until-config-lands routes before the master key is
wired in.
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptile review

@krrish-berri-2
krrish-berri-2 merged commit 52591ba into litellm_lite_up_down Jul 15, 2026
101 of 102 checks passed
@krrish-berri-2
krrish-berri-2 deleted the litellm_autoroute_qa branch July 15, 2026 21:07
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptile review

Comment thread litellm/proxy/client/cli/commands/autoroute/commands.py
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptile review

krrish-berri-2 added a commit that referenced this pull request Jul 16, 2026
…e proxy (#33231)

* feat(cli): add `lite up`/`lite down` to ambiently route Claude Code through the proxy

Patches ~/.claude/settings.json in place (env.ANTHROPIC_BASE_URL + apiKeyHelper
via `lite auth print-token`) so any `claude` session started afterward, from
any terminal, routes through the local LiteLLM proxy with no wrapper command
needed, unlike the existing `lite claude` subprocess-exec approach. Backs up
the original file first and restores it on Ctrl-C/SIGTERM, or via `lite down`
after an unclean exit. Cursor is not supported: no equivalent file-based config
to patch.

* feat(cli): add lite autoroute to QA complexity-based auto-routing against a real proxy (#33249)

* feat(cli): add lite autoroute to QA complexity-based auto-routing against a real proxy

Lets a customer try litellm's complexity_router against models they already
have on their existing, unmodified production proxy, with no config.yaml
edits and no new infra. lite autoroute configure discovers accessible
models via /model_group/info and walks through tier assignment (plus
optional LLM classifier / semantic matching / adaptive selection); every
referenced model becomes its own litellm_proxy/<name> deployment forwarding
back to the real proxy with the real key, so every actual call, routed
completions, classifier calls, embedding calls, still lands on their real
proxy. lite autoroute up launches that generated config as an ephemeral
local proxy, patches ~/.claude/settings.json to point Claude Code at it, and
streams routing decisions live; Ctrl-C/SIGTERM (or lite autoroute down
after an unclean exit) restores everything.

Also adds lite model-groups list (a thin CLI wrapper over the existing
ModelGroupsManagementClient), and generalizes up.py's settings-backup/restore
helpers to take explicit paths so this feature can reuse them instead of
duplicating the logic.

Depends on litellm_lite_up_down (#33231) for that generalization.

* feat(cli): allow multiple models per autoroute tier

complexity_router already supports a pool of models per tier (randomly
picked per request; adaptive mode specifically needs a pool to choose
within), but the configure wizard only ever let you assign one. Tiers are
now a tuple of model names; the wizard prompt accepts comma-separated
indices to pick more than one per tier.

* feat(cli): fuzzy model picker and auto-route Claude Code to autorouter

Numbered-index selection didn't scale past a handful of models, so switch
the tier picker to InquirerPy's fzf-style fuzzy search. Also set
ANTHROPIC_DEFAULT_{SONNET,HAIKU,OPUS}_MODEL to "autorouter" in Claude
Code's settings, since Router resolves auto-router deployments by literal
model name with no wildcard support, so a "*" catch-all model_name would
never match real traffic.

* feat(cli): allow installing lite CLI from source via LITELLM_CLI_REF

Lets testers try an unreleased branch's CLI changes with the same
curl-piped installer, instead of waiting for a PyPI release.

* fix(ci): modernize type hints to clear ruff strict-rule budget

* fix(ci): bump httplib2 and setuptools to patched versions

Clears osv-scan findings for PYSEC-2026-3444 and PYSEC-2026-3447.

* fix(cli): write autoroute's secret-bearing files with mode 0600

commands.py wrote config.yaml (embeds the real proxy key) and Claude
Code's settings.json (embeds the ephemeral proxy's master key) with
plain open(), landing at the umask-derived default (commonly 0644)
until a later chmod call caught up. That window, and the missed case
where settings.json already exists (chmod never ran at all there),
left a credential-bearing file readable by another local account.

secure_create() fixes the mode via fchmod on the fd before any
content is written, covering both the brand-new-file and
already-exists cases, and commands.py/wizard.py now route their
sensitive writes through it.

* docs(cli): warn that a stale Claude Code session can leak to a squatted port

lite autoroute up's master key is embedded statically (unlike lite up's
apiKeyHelper, resolved per request), so a Claude Code session still
running after teardown keeps sending it, along with prompt content, to
a now-unbound loopback port that another local account can bind. This
is the same one-time-patch tradeoff lite up already accepts, just with
a static secret instead of a re-resolved one -- document it in the
README's Caveats section and surface it in the teardown message itself.

* fix(cli): address greptile review feedback on autoroute PR

- terminate the ephemeral proxy child process when its health check
  fails, instead of leaking an orphaned, unrecoverable process bound
  to the port
- replace bare assert isinstance checks (no-ops under python -O) with
  click.ClickException in the model-groups list and configure wizard
  code paths
- close launch_proxy's log file handle once the child process has
  inherited its fd, instead of leaking it
- add build_generated_proxy_config to config.py's __all__

* fix(cli): close TOCTOU window in lite up's settings backup write

write_backup wrote the backup (which can embed the original
apiKeyHelper/settings content) with plain open() + a chmod call after
the fact -- the same permissive-until-corrected window already fixed
for autoroute's config.yaml and Claude settings writes, and missed
entirely when the backup file already exists with broader permissions.

Moves secure_create (atomic-enough 0600 via fchmod before any content
is written) to up.py, the module both lite up and lite autoroute
share, and has autoroute/process.py import it from there instead of
keeping its own copy.

* fix(cli): refuse autoroute up when a stale backup exists from a crash

The pid-record check only catches a still-live duplicate process; a
SIGKILL'd `up` leaves no live pid but does leave AUTOROUTE_BACKUP_PATH
behind. Without this guard, a fresh `up` overwrote that backup with
the currently-patched Claude settings instead of the true originals,
so `down`/Ctrl-C would restore the wrong content permanently. up.py's
`lite up` already guards the analogous case; mirror it here.

* fix(cli): bind the ephemeral autoroute proxy to loopback only

proxy_cli.py defaults --host to 0.0.0.0 when not passed explicitly.
launch_proxy never passed it, so the ephemeral proxy -- despite every
base_url in this module being built from 127.0.0.1 -- was actually
reachable from other hosts on the network, including its
unauthenticated-until-config-lands routes before the master key is
wired in.

* docs(cli): show curl install for the autoroute QA flow

Points readers at scripts/install-cli.sh's curl one-liner instead of
assuming uv/pip is already set up, and documents the LITELLM_CLI_REF
override for trying an unreleased branch or commit.

* 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`.

* fix(cli): bind lite up's apiKeyHelper to the proxy it was started against

_ensure_fresh_login only checked token freshness, not which proxy the
cached token belonged to, and resolve_api_key_helper built a bare
`lite auth print-token` command with no --base-url. A user logged into
proxy A who ran `up --base-url proxy-b` (or LITELLM_PROXY_URL=proxy-b)
would silently get proxy A's real token wired into Claude Code's
apiKeyHelper; since apiKeyHelper is invoked bare, print-token's
existing origin check never engaged, so proxy B -- attacker-controlled
or not -- received every subsequent request's Authorization header
carrying proxy A's credential.

_ensure_fresh_login now requires the cached token's base_url to match
before treating it as usable, forcing a fresh login for the selected
proxy otherwise. resolve_api_key_helper now takes that base_url and
threads it through as an explicit --base-url, so print-token's
existing (but previously unreachable in the apiKeyHelper flow)
base_url_explicit check actually enforces the match at request time
too.

* fix(cli): surface clean errors instead of raw tracebacks in lite up/down

load_json_or_empty and read_backup both delegate to pydantic's
validate_json, which raises ValidationError on invalid JSON or a
non-object root -- neither up() nor down() caught it, so a corrupt
settings or backup file surfaced an unformatted Python traceback
instead of a clean CLI error. Both now convert to UpError, and down()
(previously uncaught entirely) and up()'s teardown path now handle it.

restore_claude_settings also gained a parent.mkdir guard before
rewriting CLAUDE_SETTINGS_PATH: if ~/.claude/ was removed while `lite
up` was running, the restore would crash before deleting the backup
file, permanently stranding it and breaking every future `lite down`.

* docs(cli): call out env-var auth for autoroute commands

* fix(cli): clean up leaked proxy and surface clean errors in autoroute

Three related gaps, all following an UpError getting raised somewhere
that wasn't catching it yet:

- up() left the just-launched ephemeral proxy running with no pid
  record if load_json_or_empty/write_backup/secure_create raised after
  the health check passed, mirroring the existing ProcessLaunchError
  cleanup for the health-check-failure branch.
- _teardown() didn't catch restore_claude_settings raising UpError
  (e.g. a corrupt backup at stop time), which would otherwise escape
  to Click as an unhandled error in the normal-exit path, or print
  "Error in atexit" in the atexit path. up.py's own _restore_once
  handles the identical case the same way.
- read_pid_record let a corrupt PID file surface a raw
  pydantic.ValidationError instead of a clean message, and did so in
  down(), the command specifically meant for crash recovery. down()
  now clears an unreadable pid record and continues cleanup instead of
  aborting, since a corrupt pid file must never block the one command
  meant to recover from exactly this kind of crash.

* docs(cli): warn against running lite up and lite autoroute up together
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants