Skip to content

[Fix] Lazy feature loading under SERVER_ROOT_PATH returns 404#27812

Merged
yuneng-berri merged 2 commits into
litellm_internal_stagingfrom
litellm_lazyFeatureRootPath
May 13, 2026
Merged

[Fix] Lazy feature loading under SERVER_ROOT_PATH returns 404#27812
yuneng-berri merged 2 commits into
litellm_internal_stagingfrom
litellm_lazyFeatureRootPath

Conversation

@yuneng-berri

@yuneng-berri yuneng-berri commented May 13, 2026

Copy link
Copy Markdown
Collaborator

Relevant issues

Summary

Failure Path (Before Fix)

When the proxy is deployed under a non-empty SERVER_ROOT_PATH (e.g. /api/v1), requests to lazy-loaded endpoints such as /api/v1/policies/attachments/list or /api/v1/vector_store/list return 404. The LazyFeatureMiddleware matches the raw scope path against the registered prefixes (/policies, /vector_store/, etc.), so the prefix never matches, the feature module is never imported, and the route is never registered. Starlette's router then 404s because the route doesn't exist. Non-lazy routes work because they're registered at startup.

Fix

Strip the configured server root path from the request path before the prefix check. The check normalizes a trailing slash on the env var, skips the strip when the value is /, and uses a component boundary (prefix + "/") so /api does not falsely match /apiv2. If the path doesn't carry the prefix (reverse proxy already stripped it), it is left alone.

Testing

  • Reproduced the 404 locally with SERVER_ROOT_PATH=/api/v1 against /api/v1/policies/attachments/list and /api/v1/vector_store/list; both return 200 after the fix.
  • Added a parametrized test (test_root_path_handling) covering: prefix strip + match, trailing-slash env, pre-stripped path (reverse proxy), no root path, root path /, boundary collision (/api vs /apiv2), and unrelated paths under the root.
  • uv run pytest tests/test_litellm/proxy/test_proxy_server.py::TestLazyFeatureMiddleware — 10 passed.

Type

🐛 Bug Fix
✅ Test

Screenshots


Note

Medium Risk
Changes request-path matching in LazyFeatureMiddleware based on SERVER_ROOT_PATH, which could affect when optional routers are imported/registered and thus routing behavior for some deployments.

Overview
Fixes lazy feature loading when the proxy is deployed behind a non-empty SERVER_ROOT_PATH by stripping the configured root path from the incoming request path before prefix/suffix matching, with normalization for trailing slashes and a boundary check to avoid false matches.

Adds a parametrized test (test_root_path_handling) covering root-path stripping, reverse-proxy pre-stripped paths, SERVER_ROOT_PATH='/' no-op behavior, boundary collisions, and non-matching paths.

Reviewed by Cursor Bugbot for commit 384fc0b. Bugbot is set up for automated code reviews on this repo. Configure here.

LazyFeatureMiddleware compared the raw scope path against registered
prefixes (e.g. /policies), so requests under a server root path like
/api/v1/policies/... never matched, the feature never loaded, and the
endpoint returned 404. Strip the configured root path before matching,
normalizing trailing slashes and enforcing a component boundary so
/api does not falsely match /apiv2.
@greptile-apps

greptile-apps Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a 404 regression for lazy-loaded feature endpoints when the proxy is deployed under a non-empty SERVER_ROOT_PATH. The LazyFeatureMiddleware was matching raw scope paths against feature prefixes, so /api/v1/policies/... never matched /policies/... and the feature module was never registered.

  • _lazy_features.py: SERVER_ROOT_PATH is now normalized and cached as self._root_path in __init__, then stripped from the request path before prefix matching. A + \"/\" boundary guard avoids false stripping of paths that share a common prefix with the root path (e.g. /apiv2 when root is /api), and the strip is skipped when the root path is / or when a reverse proxy has already removed it.
  • test_proxy_server.py: Seven parametrized cases are added covering the strip-and-match, trailing-slash normalization, pre-stripped path, no root path, root-is-slash, boundary collision, and unrelated-path scenarios.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to path normalization inside the lazy-loading middleware and does not touch auth, routing logic, or any critical request path beyond feature-load triggering.

The fix is a small, well-commented addition: a single string strip gated on a clearly defensive condition, with the computed value cached at construction time. The test suite covers the meaningful edge cases and no existing tests are weakened. No auth, data, or migration paths are affected.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/_lazy_features.py Caches normalized SERVER_ROOT_PATH in init and strips it from the request path before prefix matching; correctly handles trailing-slash, root-path-is-slash, and pre-stripped-by-proxy edge cases.
tests/test_litellm/proxy/test_proxy_server.py Adds 7 parametrized cases covering the root-path strip; one case ("boundary check prevents false match") is labeled correctly but does not exercise the boundary guard in a way that would fail without it.

Reviews (2): Last reviewed commit: "Cache normalized SERVER_ROOT_PATH at mid..." | Re-trigger Greptile

@codecov

codecov Bot commented May 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

SERVER_ROOT_PATH is a process-startup env var. Read it once in
__init__ instead of calling get_server_root_path() + rstrip on every
request that arrives before all lazy features have loaded.
@yuneng-berri

Copy link
Copy Markdown
Collaborator Author

@greptile

@mateo-berri

Copy link
Copy Markdown
Collaborator

bugbot run

@mateo-berri mateo-berri left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM; thanks!

@cursor cursor 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 384fc0b. Configure here.

@yuneng-berri yuneng-berri merged commit 431daa1 into litellm_internal_staging May 13, 2026
113 of 115 checks passed
@yuneng-berri yuneng-berri deleted the litellm_lazyFeatureRootPath branch May 13, 2026 04:35
yuneng-berri added a commit that referenced this pull request May 13, 2026
[Fix] Lazy feature loading under SERVER_ROOT_PATH returns 404 (backport of #27812)
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.

2 participants