Skip to content

fix(install.sh): fix pipx strategy to avoid PEP 668 errors#24327

Open
shivamrawat1 wants to merge 4 commits intomainfrom
litellm_wizard_script_fix
Open

fix(install.sh): fix pipx strategy to avoid PEP 668 errors#24327
shivamrawat1 wants to merge 4 commits intomainfrom
litellm_wizard_script_fix

Conversation

@shivamrawat1
Copy link
Collaborator

@shivamrawat1 shivamrawat1 commented Mar 21, 2026

Updates scripts/install.sh so installing litellm[proxy] works on PEP 668 “externally managed environment” setups (e.g. Homebrew Python, many Linux distros) where pip install into the system/user interpreter is blocked or discouraged.

What changed

Strategy A — pipx (when available)
Prefer pipx upgrade litellm so existing pipx installs keep their options (e.g. --include-deps).
After a successful upgrade, run pipx runpip litellm install "litellm[proxy]" so proxy extras are present even if the user originally installed plain litellm (non-fatal if it fails).
If nothing is installed under pipx yet, fall back to pipx install litellm[proxy].
Strategy B — dedicated venv (~/.litellm/venv by default, overridable via LITELLM_VENV)
python -m venv --clear, upgrade pip inside the venv, then pip install litellm[proxy].
Avoids touching the system Python’s site-packages.
Removed the unconditional “system pip must exist” gate; pipx manages its own envs and the venv path uses the venv’s bundled pip/ensurepip.
pipx discovery fixes
Default PIPX_HOME fallback is ~/.local/share/pipx (not ~/.local), matching standard pipx layout.
Binary search uses PIPX_BIN_DIR and $PIPX_HOME/venvs/litellm/bin/litellm (removed the mistaken extra path segment).
PATH hint uses the directory of the resolved litellm binary (dirname), not a hardcoded scripts path.

Cause
PEP 668: OS/distro Python is marked “externally managed”; pip install to that environment fails or is unsafe. The old script assumed python -m pip install always worked after a pip check.
Wrong pipx defaults: Treating PIPX_HOME as ~/.local made fallback paths wrong and error messages misleading; standard pipx stores venvs under ~/.local/share/pipx.
pipx install --force: Reinstalling without preserving prior pipx flags could drop behavior such as --include-deps for users who had used it before.

Relevant issues

Pre-Submission checklist

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

  • I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Delays in PR merge?

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

CI (LiteLLM team)

CI status guideline:

  • 50-55 passing tests: main is stable with minor issues.
  • 45-49 passing tests: acceptable but needs attention
  • <= 40 passing tests: unstable; be careful with your merges and assess the risk.
  • Branch creation CI run
    Link:

  • CI run for the last commit
    Link:

  • Merge / cherry-pick CI run
    Links:

Type

🆕 New Feature
🐛 Bug Fix

Changes

Area Problem Fix
Install target Direct install into system/user Python hits PEP 668 Prefer pipx; else isolated venv at ~/.litellm/venv with --clear
pip prerequisite Required system pip even when not used for final install Drop unconditional gate; venv/pipx supply pip where needed
Existing pipx users Force reinstall could reset options pipx upgrade litellm first
Proxy extras Upgrade alone might leave bare litellm without [proxy] pipx runpip litellm install litellm[proxy] inside the pipx venv (doesn’t replace pipx’s install metadata like a full --force reinstall)
Binary location Wrong PIPX_HOME default + extra bad path Default ${HOME}/.local/share/pipx, search PIPX_BIN_DIR + $PIPX_HOME/venvs/litellm/bin/litellm, then command -v litellm
User guidance PATH hint could be wrong export PATH="$PATH:$(dirname "$LITELLM_BIN")"

Replace direct `pip install` with a two-strategy approach that works on
PEP 668 "externally-managed-environment" systems (Homebrew Python,
Ubuntu 24.04+, Fedora 39+):

Strategy 1 — pipx: try `upgrade` first (preserves existing flags like
`--include-deps`), then `runpip` to ensure proxy extras are present,
falling back to fresh `install` if not yet managed by pipx.

Strategy 2 — venv: create an isolated venv at ~/.litellm/venv with
built-in ensurepip (no system pip required).

Also fixes:
- Wrong `_pipx_home` default (~/.local → ~/.local/share/pipx)
- Dead fallback binary search paths that never matched standard installs
- PATH hint now computed dynamically from the resolved binary

Made-with: Cursor
@vercel
Copy link

vercel bot commented Mar 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Mar 21, 2026 11:20pm

Request Review

@codspeed-hq
Copy link
Contributor

codspeed-hq bot commented Mar 21, 2026

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing litellm_wizard_script_fix (4611d9b) with main (e3d4c29)

Open in CodSpeed

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 21, 2026

Greptile Summary

This PR updates scripts/install.sh to avoid PEP 668 "externally managed environment" errors by replacing the direct pip install approach with two ordered strategies: pipx (preferred for isolated CLI app installs) and a dedicated venv at ~/.litellm/venv. It also fixes incorrect PIPX_HOME defaults and binary lookup paths, and adds a small regression test suite that validates bash syntax and key messaging strings.

Key changes:

  • Removed the unconditional system-pip existence gate; pipx and venv each supply their own pip.
  • Strategy 1 (pipx): tries pipx upgrade litellm first to preserve existing flags, then falls back to pipx install litellm[proxy].
  • Uses pipx inject litellm litellm[proxy] after upgrade to ensure proxy extras are present.
  • Strategy 2 (venv): creates ~/.litellm/venv (preserving an existing venv on re-runs, honoring LITELLM_FORCE_VENV_RECREATE=1).
  • Corrects PIPX_HOME default from ~/.local to ~/.local/share/pipx and removes a spurious extra path segment.
  • Adds warn() in yellow distinct from info() for failure messaging.

Issues found:

  • pipx inject litellm litellm[proxy] records the same package as an injected dependency in pipx metadata. A plain pipx upgrade litellm (without --include-injected) will not upgrade the proxy extras in the future, leaving them potentially out of sync with the core package. Users are not warned about this.
  • The venv path immediately calls ${LITELLM_VENV}/bin/pip without checking if pip was bootstrapped; on minimal Debian/Ubuntu environments, venv can succeed while leaving bin/pip absent (only bin/pip3 present).
  • The new test only runs bash -n for syntax validation. Given the script's own comments explicitly state it must be POSIX sh/dash compatible (invoked as sh via curl | sh), a dash -n check should also be included to catch bash-isms that would fail at runtime under dash.

Confidence Score: 3/5

  • Safe to merge in terms of not breaking existing functionality, but contains a subtle pipx metadata issue and a venv bootstrap gap that could leave users with a broken or stale proxy install.
  • The overall direction is sound and addresses the PEP 668 problem correctly. However, pipx inject litellm litellm[proxy] creates a circular pipx metadata entry that silently breaks future pipx upgrade litellm flows for proxy extras. The venv pip bootstrap step doesn't guard against pip absence on minimal Debian/Ubuntu setups. The test only validates bash syntax, not POSIX sh compatibility which the script explicitly targets.
  • scripts/install.sh — particularly lines 99–104 (pipx inject circular metadata) and lines 151–152 (unguarded bin/pip assumption in the venv strategy).

Important Files Changed

Filename Overview
scripts/install.sh Replaces direct pip install with a two-strategy approach (pipx → venv) to avoid PEP 668 errors. Good overall improvement, but pipx inject litellm litellm[proxy] creates circular pipx metadata that breaks future plain-pipx upgrade flows, and the venv bootstrap doesn't guard against pip absence after venv creation.
tests/test_litellm/scripts/test_install_sh.py New static regression test suite for install.sh. Only runs bash -n for syntax validation, missing a dash/sh syntax check despite the script explicitly supporting POSIX sh invocation via `curl

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Start install.sh] --> B{Python 3.9+ found?}
    B -- No --> Z1[die: Python not found]
    B -- Yes --> C{pipx on PATH?}

    C -- Yes --> D[info: Using pipx]
    D --> E{pipx upgrade litellm 2>/dev/null}
    E -- Success --> F[pipx inject litellm litellm proxy]
    F -- Fail --> G[warn: could not inject proxy extras]
    F -- Success --> H[_pipx_upgraded=1]
    G --> H
    E -- Fail --> I{pipx install litellm proxy}
    I -- Success --> H
    I -- Fail --> J[warn: pipx install failed]
    J --> K[LITELLM_BIN still empty]

    H --> L{Search binary at PIPX_BIN_DIR, PIPX_HOME/venvs}
    L -- Found --> M[LITELLM_BIN set]
    L -- Not found --> N{command -v litellm?}
    N -- Yes --> M
    N -- No --> O[warn: binary not found, falling back to venv]
    O --> K

    C -- No --> K
    K --> P{LITELLM_BIN empty?}
    M --> Q{LITELLM_BIN empty?}

    P -- Yes --> R[Strategy 2: venv at ~/.litellm/venv]
    R --> S{LITELLM_FORCE_VENV_RECREATE=1?}
    S -- Yes --> T[venv --clear]
    S -- No --> U{venv dir exists?}
    U -- No --> V[venv create]
    U -- Yes --> W{bin/python executable?}
    W -- No --> T
    W -- Yes --> X[reuse existing venv]
    T --> Y
    V --> Y
    X --> Y
    Y[pip install --upgrade pip] --> AA[pip install litellm proxy]
    AA --> AB[LITELLM_BIN = venv/bin/litellm]

    AB --> Q
    Q -- No --> AC{litellm binary executable?}
    Q -- Yes --> AC
    AC -- No --> Z2[die: binary not found]
    AC -- Yes --> AD[success banner + PATH hint]
    AD --> AE[Setup wizard prompt]
Loading

Last reviewed commit: "fix(install.sh): use..."

- Warn and show output when pipx runpip fails to add proxy extras
- Fall back to venv when pipx succeeds but litellm binary is missing
- Reuse existing venv by default; LITELLM_FORCE_VENV_RECREATE for full reset

Made-with: Cursor
@shivamrawat1
Copy link
Collaborator Author

@greptile review with the new commit that resolves above comments

- Use yellow for warn() so it is distinct from info()
- Document why runpip is used instead of pipx inject (older pipx)
- Warn when pipx succeeds but binary is missing before venv fallback

Made-with: Cursor
@shivamrawat1
Copy link
Collaborator Author

@gerptile review with new commit that resolves the raised issues and give the score out of 5. Note this specific PR does not need a test to be added so dont consider that in the score.

Add unit tests for install script syntax and pipx paths.

Made-with: Cursor
@shivamrawat1
Copy link
Collaborator Author

@greptile review and give a score out of 5

Comment on lines +29 to +36
def test_install_sh_passes_bash_syntax_check():
result = subprocess.run(
["bash", "-n", str(_INSTALL_SH)],
capture_output=True,
text=True,
check=False,
)
assert result.returncode == 0, result.stderr or result.stdout
Copy link
Contributor

Choose a reason for hiding this comment

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

P1 bash -n doesn't validate POSIX sh compatibility

The test only runs bash -n for syntax checking, but the install script's own usage comment says it is invoked as sh (i.e. curl … | sh), and the script explicitly notes that on Ubuntu/Debian dash is used when invoked this way. Bash is significantly more permissive than POSIX sh / dash — for instance, bash -n will happily accept bash-isms like arrays, [[, local declarations, process substitutions, and pipefail that would fail or behave differently under dash.

Since the whole point of the set -eu (not set -euo pipefail) note in the script is POSIX sh compatibility, a bash -n syntax check does not validate the thing the authors are explicitly guarding for. A dash -n or sh -n check (when dash is available) would surface any constructs that only work in bash. Consider running both:

def test_install_sh_passes_bash_syntax_check():
    result = subprocess.run(
        ["bash", "-n", str(_INSTALL_SH)],
        capture_output=True, text=True, check=False,
    )
    assert result.returncode == 0, result.stderr or result.stdout


@pytest.mark.skipif(
    subprocess.run(["which", "dash"], capture_output=True).returncode != 0,
    reason="dash not available",
)
def test_install_sh_passes_dash_syntax_check():
    result = subprocess.run(
        ["dash", "-n", str(_INSTALL_SH)],
        capture_output=True, text=True, check=False,
    )
    assert result.returncode == 0, result.stderr or result.stdout

Comment on lines +99 to +104
if ! _inject_err="$(pipx inject litellm "${LITELLM_PACKAGE}" 2>&1)"; then
warn "could not inject proxy extras (proxy features may fail until manually fixed)."
printf '%s\n' "$_inject_err" >&2
fi
unset _inject_err 2>/dev/null || true
_pipx_upgraded=1
Copy link
Contributor

Choose a reason for hiding this comment

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

P1 pipx inject of same package skips extras on future upgrades

pipx inject litellm litellm[proxy] records litellm[proxy] as an injected dependency inside the litellm pipx environment's metadata. This has a concrete maintenance consequence: a future plain pipx upgrade litellm only upgrades the main app, not injected packages. Users must know to run pipx upgrade litellm --include-injected to keep the proxy extras in sync with the upgraded core package.

In practice this means:

  • A user who installed via this script and later runs pipx upgrade litellm gets a newer litellm core but stale proxy extra dependencies.
  • pipx list shows litellm appearing as both the main app and an injected package, which is unusual and confusing.

The upgrade note should at minimum be surfaced in the success message or as an info hint. For example, after a successful pipx install path, add:

info "To upgrade later, run: pipx upgrade litellm --include-injected"

This ensures users don't lose the proxy extras on their next upgrade cycle.

Comment on lines +151 to +152
"${LITELLM_VENV}/bin/pip" install -q --upgrade pip \
|| die "Failed to upgrade pip in venv. Try: ${LITELLM_VENV}/bin/pip install --upgrade pip"
Copy link
Contributor

Choose a reason for hiding this comment

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

P1 venv/bin/pip may not exist before pip upgrade step

The venv creation path falls through three branches (lines 137–150): LITELLM_FORCE_VENV_RECREATE=1, fresh directory, and incomplete-venv recreation. After any of them, the script reaches:

"${LITELLM_VENV}/bin/pip" install -q --upgrade pip

Python's venv module uses ensurepip to bootstrap pip. On some minimal environments (e.g. Ubuntu/Debian without the python3-venv package and without pip pre-installed), venv can succeed but create the venv without bin/pip — only bin/python and bin/pip3 may be present, depending on the Python build. If ${LITELLM_VENV}/bin/pip does not exist, this line fails with a confusing "not found" error rather than the die message.

The already-existing guard on line 145 ([ ! -x "${LITELLM_VENV}/bin/python" ]) checks for python, not pip. Consider adding an explicit check after venv creation:

if [ ! -x "${LITELLM_VENV}/bin/pip" ] && [ ! -x "${LITELLM_VENV}/bin/pip3" ]; then
  die "pip was not bootstrapped in the venv. Try: sudo apt install python3-pip python3-venv, then re-run."
fi
VENV_PIP="${LITELLM_VENV}/bin/pip3"
[ -x "${LITELLM_VENV}/bin/pip" ] && VENV_PIP="${LITELLM_VENV}/bin/pip"
"${VENV_PIP}" install -q --upgrade pip ...

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.

1 participant