Skip to content

ci: guard against MAX_PATH-busting packaged wheel paths - #29587

Closed
roman-vm wants to merge 2 commits into
BerriAI:litellm_oss_branchfrom
roman-vm:fix/ci-wheel-path-length-guard
Closed

ci: guard against MAX_PATH-busting packaged wheel paths#29587
roman-vm wants to merge 2 commits into
BerriAI:litellm_oss_branchfrom
roman-vm:fix/ci-wheel-path-length-guard

Conversation

@roman-vm

@roman-vm roman-vm commented Jun 3, 2026

Copy link
Copy Markdown

What

Adds a CI check that builds the wheel and fails if any packaged path is long enough to risk the Windows 260-char MAX_PATH limit at install time (default threshold: 160 chars inside the wheel).

  • .github/scripts/check_wheel_path_length.py — opens the built *.whl and reports/fails on over-long entries.
  • .github/workflows/check-wheel-path-length.ymluv build --wheel then runs the check.

Why

This is the durable counterpart to #29553. The content-filter benchmark fixtures have now broken pip install litellm on default Windows three times (#21941#22039#29536 / #29553), each fixed by renaming. A guard makes the constraint explicit so it can't regress a fourth time.

Because the check runs on the built wheel, it honours [tool.uv.build-backend].source-exclude — i.e. it passes as soon as the offending fixtures are excluded/shortened, and trips if anything over-long gets packaged again.

Threshold rationale

The installed path is <site-packages prefix> + <path inside wheel>. A realistic worst-case Windows prefix (long profile name + roaming AppData venv) is ~95 chars:

C:\Users\Administrator\AppData\Roaming\<app>\<sub>\venv\Lib\site-packages\

So we budget 260 − 100 = 160 for the in-wheel path. (The real-world break in #29536 was a 176-char in-wheel path → 266 on disk.)

Notes

@roman-vm
roman-vm requested a review from a team June 3, 2026 13:41
@CLAassistant

CLAassistant commented Jun 3, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@greptile-apps

greptile-apps Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a CI guard that builds the litellm wheel and fails if any packaged path exceeds 160 characters, preventing a recurrence of the Windows MAX_PATH breakage that has regressed three times via long benchmark fixture filenames.

  • A new Python script (.github/scripts/check_wheel_path_length.py) opens the built .whl as a zip, reports all entries over the threshold, and exits non-zero on any violation.
  • A new GitHub Actions workflow (.github/workflows/check-wheel-path-length.yml) builds the wheel with uv build --wheel and then invokes the check; it triggers on all PRs and pushes to main/litellm_oss_branch.

Confidence Score: 4/5

Safe to merge — adds only a new CI check with no changes to library code or existing workflows.

Both files are net-new and purely additive. The check script has a minor resource-management issue (ZipFile not wrapped in a context manager) and the workflow's broad pull_request trigger may fire on more branches than intended, but neither affects correctness or security of the guard itself.

.github/scripts/check_wheel_path_length.py for the unclosed ZipFile handle; .github/workflows/check-wheel-path-length.yml to confirm the intended trigger scope.

Important Files Changed

Filename Overview
.github/scripts/check_wheel_path_length.py New guard script: opens built wheel as a zip, reports entries exceeding 160-char threshold, exits non-zero on violations. Minor: ZipFile opened without a context manager.
.github/workflows/check-wheel-path-length.yml New CI workflow: builds wheel with uv then runs the path-length check. Triggers on all PRs (no branch filter) and pushes to main/litellm_oss_branch; no Python version pinned but the check script requires only stdlib.

Reviews (1): Last reviewed commit: "ci: run wheel path-length guard on PRs" | Re-trigger Greptile

Comment on lines +38 to +52
for whl in wheels:
names = zipfile.ZipFile(whl).namelist()
longest = max((len(n) for n in names), default=0)
offenders = sorted(
(n for n in names if len(n) > MAX_RELATIVE), key=len, reverse=True
)
print(f"{os.path.basename(whl)}: {len(names)} entries, longest path = {longest}")
if offenders:
rc = 1
print(
f"::error::{len(offenders)} packaged path(s) exceed {MAX_RELATIVE} chars "
f"and risk the Windows MAX_PATH limit at install time:"
)
for n in offenders[:15]:
print(f" {len(n):4} {n}")

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.

P2 The ZipFile is opened without a context manager, so the file handle is never explicitly closed. On Windows, this can hold a file lock for longer than necessary (until GC), which can matter if the script is extended to write or move wheel files later. Use with to guarantee cleanup.

Suggested change
for whl in wheels:
names = zipfile.ZipFile(whl).namelist()
longest = max((len(n) for n in names), default=0)
offenders = sorted(
(n for n in names if len(n) > MAX_RELATIVE), key=len, reverse=True
)
print(f"{os.path.basename(whl)}: {len(names)} entries, longest path = {longest}")
if offenders:
rc = 1
print(
f"::error::{len(offenders)} packaged path(s) exceed {MAX_RELATIVE} chars "
f"and risk the Windows MAX_PATH limit at install time:"
)
for n in offenders[:15]:
print(f" {len(n):4} {n}")
for whl in wheels:
with zipfile.ZipFile(whl) as zf:
names = zf.namelist()
longest = max((len(n) for n in names), default=0)
offenders = sorted(
(n for n in names if len(n) > MAX_RELATIVE), key=len, reverse=True
)
print(f"{os.path.basename(whl)}: {len(names)} entries, longest path = {longest}")
if offenders:
rc = 1
print(
f"::error::{len(offenders)} packaged path(s) exceed {MAX_RELATIVE} chars "
f"and risk the Windows MAX_PATH limit at install time:"
)
for n in offenders[:15]:
print(f" {len(n):4} {n}")

# #22039, #29536, #29553).

on:
pull_request:

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.

P2 Workflow triggers on all PRs regardless of target branch

The pull_request: trigger has no branches: filter, so this job fires on PRs targeting any branch (not just main/litellm_oss_branch). That's likely intentional as a broad guard, but worth confirming — if the intent is to match the push: filter, a branches: restriction here would keep the scope consistent.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@codecov

codecov Bot commented Jun 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@yuneng-berri

Copy link
Copy Markdown
Contributor

Thanks for this! We already have a ci step inside of Circle CI that deals with Windows, so I think that is a better home for these. I expanded the tests to cover this case. Thanks for bringing this to our attention!

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