BerriAI/litellm · error · UpError

No fresh LiteLLM login found for this proxy. Run `lite login

Error message

No fresh LiteLLM login found for this proxy. Run `lite login` first (apiKeyHelper reads this token on every Claude Code request).

What it means

Raised by `lite up`'s _ensure_fresh_login when stdin is not a TTY and the cached CLI token (~/.litellm/token.json) is missing, was issued for a different proxy base_url, or is stale per is_cli_token_fresh (age >= CLI_JWT_EXPIRATION_HOURS minus a 0.1h buffer). In an interactive terminal `up` would offer to start the login flow; with no TTY it cannot, so it fails fast and asks you to run `lite login` first. The token matters because Claude Code's apiKeyHelper re-reads it on every request via `lite auth print-token --base-url`.

Source

Thrown at litellm/proxy/client/cli/commands/up.py:166

    whichever server `up` currently points at.
    """
    lite_path: Final = shutil.which("lite")
    if lite_path is None:
        raise UpError(
            "Could not find `lite` on your PATH. Claude Code's apiKeyHelper needs "
            "an absolute path to it, so `lite up` cannot continue."
        )
    return f"{shlex.quote(lite_path)} auth print-token --base-url {shlex.quote(base_url)}"


def _ensure_fresh_login(ctx: click.Context) -> None:
    base_url: Final = ctx.obj["base_url"].rstrip("/")
    token_data = load_token()
    if token_data and token_data.get("base_url") == base_url and is_cli_token_fresh(token_data):
        return

    if not sys.stdin.isatty():
        raise UpError(
            "No fresh LiteLLM login found for this proxy. Run `lite login` first (apiKeyHelper "
            "reads this token on every Claude Code request)."
        )

    click.echo("No fresh LiteLLM login found for this proxy; starting login...")
    ctx.invoke(login)
    token_data = load_token()
    if not token_data or token_data.get("base_url") != base_url or not is_cli_token_fresh(token_data):
        raise UpError("Login did not produce a usable token; cannot start `lite up`.")


def _restore_and_report() -> None:
    record: Final = restore_claude_settings()
    if record is None:
        click.echo("Nothing to restore.")
        return
    if record.existed:
        click.echo(f"Restored {CLAUDE_SETTINGS_PATH} to its original contents.")

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Run `lite login` interactively against the same proxy (`--base-url` / LITELLM_PROXY_URL must match what `up` uses), complete the browser flow, then rerun `lite up`
  2. If it still fails, inspect ~/.litellm/token.json: the `base_url` field must equal the proxy URL (trailing slash stripped) and `timestamp` must be recent
  3. In non-interactive pipelines, re-login before the token expires rather than after — schedule the interactive `lite login` or pre-provision token.json on the runner

Example fix

# before (CI job, no TTY)
lite up   # UpError: No fresh LiteLLM login found for this proxy ...

# after
# one-time on a workstation with a browser:
lite login --base-url https://proxy.internal
curl -s ~/.litellm/token.json -o token.json   # copy to the runner, keep mode 0600
lite up
Defensive patterns

Strategy: validation

Validate before calling

import json, os, time

def cli_token_is_fresh(base_url: str, max_age_hours: float) -> bool:
    p = os.path.expanduser("~/.litellm/token.json")
    if not os.path.exists(p):
        return False
    try:
        t = json.load(open(p))
    except (OSError, json.JSONDecodeError):
        return False
    ts = t.get("timestamp")
    return (
        t.get("base_url") == base_url.rstrip("/")
        and isinstance(ts, (int, float))
        and (time.time() - ts) / 3600 < max_age_hours
    )

# check with litellm.constants.CLI_JWT_EXPIRATION_HOURS as max_age_hours before `lite up`

Try / catch

import subprocess, sys
if not cli_token_is_fresh(BASE_URL, MAX_AGE_H):
    if not sys.stdin.isatty():
        sys.exit("run `lite login` interactively before this non-interactive job")
    subprocess.check_call(["lite", "login"])
subprocess.check_call(["lite", "up"])

Prevention

When it happens

Trigger: Running `lite up` in a script, CI job, nohup, or `ssh -T` (stdin not a TTY) after the cached JWT expired, after the token file was deleted (`lite logout`), after switching --base-url/LITELLM_PROXY_URL to a proxy never logged into, or when token.json's base_url doesn't match the current proxy URL.

Common situations: Long-lived CI runner where the token aged past the CLI JWT expiry window; developer pointing the CLI at a second proxy (staging vs prod) without re-logging in; cleanup step wiping ~/.litellm; token minted for http://host:4000 while the CLI now targets http://host:4000/ (trailing-slash mismatches are normalized, host/port ones are not).

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/0032d38528004868. Report an issue: GitHub.