Skip to content

fix(passthrough): stop request params from clobbering merged target query params - #32404

Merged
mateo-berri merged 2 commits into
litellm_internal_stagingfrom
litellm_fix_merge_query_params_clobber
Jul 8, 2026
Merged

fix(passthrough): stop request params from clobbering merged target query params#32404
mateo-berri merged 2 commits into
litellm_internal_stagingfrom
litellm_fix_merge_query_params_clobber

Conversation

@mateo-berri

@mateo-berri mateo-berri commented Jul 8, 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)

Screenshots / Proof of Fix

Live proxy, real Anthropic API (models list endpoint, no mocks). Config used, note the ?limit=1 baked into the target plus merge_query_params: true:

general_settings:
  master_key: sk-1234
  pass_through_endpoints:
    - path: "/anthropic-models"
      target: "https://api.anthropic.com/v1/models?limit=1"
      merge_query_params: true
      headers:
        x-api-key: os.environ/ANTHROPIC_API_KEY
        anthropic-version: "2023-06-01"

Proxy started with .venv/bin/python litellm/proxy/proxy_cli.py --config proof_config.yaml --host 127.0.0.1 --port 49413 (with ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN unset)

Before the fix, at base commit db24027, the target's limit=1 never reaches Anthropic; the plain call returns the full default page and the paged call returns everything after the cursor:

$ git rev-parse HEAD
db2402754aac87e58cd7154070b0464c5c82f482

$ curl -s "http://127.0.0.1:49413/anthropic-models" -H "Authorization: Bearer sk-1234" \
    | python3 -c "import json,sys; d=json.load(sys.stdin); print('num models returned:', len(d['data'])); print('has_more:', d['has_more'])"
num models returned: 10
has_more: False

$ curl -s "http://127.0.0.1:49413/anthropic-models?after_id=claude-sonnet-5" -H "Authorization: Bearer sk-1234" \
    | python3 -c "import json,sys; d=json.load(sys.stdin); print('num models returned:', len(d['data'])); print('ids:', [m['id'] for m in d['data']]); print('has_more:', d['has_more'])"
num models returned: 9
ids: ['claude-fable-5', 'claude-opus-4-8', 'claude-opus-4-7', 'claude-sonnet-4-6', 'claude-opus-4-6', 'claude-opus-4-5-20251101', 'claude-haiku-4-5-20251001', 'claude-sonnet-4-5-20250929', 'claude-opus-4-1-20250805']
has_more: False

After the fix, at commit dd989af, the same curls show limit=1 surviving on the wire and merging with the client's after_id:

$ git rev-parse HEAD
dd989af0d524742450fe22b10a5aeb4d142227f2

$ curl -s "http://127.0.0.1:49413/anthropic-models" -H "Authorization: Bearer sk-1234" \
    | python3 -c "import json,sys; d=json.load(sys.stdin); print('num models returned:', len(d['data'])); print('ids:', [m['id'] for m in d['data']]); print('has_more:', d['has_more'])"
num models returned: 1
ids: ['claude-sonnet-5']
has_more: True

$ curl -s "http://127.0.0.1:49413/anthropic-models?after_id=claude-sonnet-5" -H "Authorization: Bearer sk-1234" \
    | python3 -c "import json,sys; d=json.load(sys.stdin); print('num models returned:', len(d['data'])); print('ids:', [m['id'] for m in d['data']]); print('has_more:', d['has_more'])"
num models returned: 1
ids: ['claude-fable-5']
has_more: True

Type

🐛 Bug Fix

Changes

pass_through_request folds the target URL's own query params (and any configured default_query_params) into the outgoing URL when merge_query_params or default_query_params is set, but then still passed the incoming request's query params to httpx via params=. httpx's params= replaces the URL's entire query string, so the merged query was clobbered down to just the incoming request's params and the target's own query never reached the wire. Because dict(request.query_params) is {} rather than None when the client sends no params, even paramless requests stripped the target's query, so merge_query_params has effectively never worked on the wire

The fix computes the effective incoming params once (query_params or dict(request.query_params), the same expression the wire previously used), folds them into the merged URL with highest precedence, and sets requested_query_params to None so httpx sends the merged URL untouched. This matches the documented default_query_params semantics (defaults sent with every request, overridable per key by the client) and also covers the default_query_params-only case, where incoming params previously were not folded into the merged URL at all. Endpoints with neither option keep their existing behavior of the incoming params replacing the target query. The streaming, multipart, and raw-body call sites all consume the same requested_query_params variable, so they are all fixed by this single change

The existing test for this feature only inspected the url kwarg handed to httpx.AsyncClient.request, before httpx applies params=, which is why it never caught the clobbering. The new regression tests assert the final wire URL instead, by injecting an httpx.MockTransport-backed client into litellm's client cache so the real request-building path runs: merge preserves target plus incoming params, defaults reach the wire with per-key client overrides winning, and no-merge endpoints keep replace semantics. The first two tests fail on the base commit and pass with the fix

The follow-up commit fixes an ordering regression the first commit introduced on merge-enabled endpoints that use passthrough managed object IDs: the fold ran before the managed-ID input rewrite, so rewrite_query_ids received None while the un-rewritten managed ID was already baked into the URL and would have leaked upstream. The fold-and-null now runs after the managed-ID rewrite block, so the rewritten query params are what gets merged into the URL; nothing in between depends on the URL's query string (endpoint type detection keys on host and route substrings). A new regression test drives a merge-enabled endpoint with a managed ID in a query param through a fake managed_files hook injected via the same proxy_logging_obj.get_proxy_hook seam production uses, and asserts the wire URL carries the rewritten raw ID together with the target's own params; it fails without the reordering. The test helper's cache-key lookup was also tightened per review to next(..., None) plus an assert with an explanatory message instead of an opaque StopIteration

@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes a bug where merge_query_params and default_query_params on passthrough endpoints never reached the wire: the merged URL was built correctly but then immediately overwritten because httpx's params= replaces the entire query string. The fix moves the merge block after the managed-ID rewrite so rewritten params are baked in, then sets requested_query_params = None so httpx forwards the merged URL untouched.

  • Core fix: requested_query_params is computed once upfront (line 823), fed through the managed-ID rewrite unchanged, then folded into the URL by get_merged_query_parameters; requested_query_params is then nulled so the params= kwarg no longer clobbers the URL on the wire.
  • Ordering fix: the merge-and-null step now runs after rewrite_query_ids, so managed IDs in incoming query params are resolved to raw provider IDs before being baked into the URL.
  • New tests: four httpx.MockTransport-backed regression tests assert the final wire URL for merge, default_query_params, no-merge, and managed-ID-with-merge scenarios; the first two fail on the base commit and pass with the fix.

Confidence Score: 5/5

Safe to merge — the change is a targeted fix to a broken feature with no side effects on endpoints that don't use merge_query_params or default_query_params.

The logic is sound: requested_query_params is guaranteed to be a dict (never None) when it enters the merge block, the ordering of managed-ID rewrite before merge is now correct, and the null assignment after merge cleanly prevents httpx from overwriting the URL. No-merge endpoints are entirely unaffected. The four new wire-level tests directly demonstrate the previously-broken and now-fixed behaviors on the real request-building path.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/pass_through_endpoints/pass_through_endpoints.py Fixes query-param merge bug: moves merge block after managed-ID rewrite, sets requested_query_params=None so httpx does not clobber the already-merged URL query string
tests/test_litellm/proxy/pass_through_endpoints/test_pass_through_endpoints.py Adds four regression tests using httpx.MockTransport to assert the final wire URL; covers merge, default_query_params, no-merge, and managed-ID-rewrite-with-merge scenarios

Reviews (2): Last reviewed commit: "fix(passthrough): rewrite managed ids in..." | Re-trigger Greptile

Comment thread tests/test_litellm/proxy/pass_through_endpoints/test_pass_through_endpoints.py Outdated
@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment thread litellm/proxy/pass_through_endpoints/pass_through_endpoints.py Outdated
@veria-ai

veria-ai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

@codspeed-hq

codspeed-hq Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 30 untouched benchmarks


Comparing litellm_fix_merge_query_params_clobber (33b6989) with litellm_internal_staging (db24027)

Open in CodSpeed

@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@mateo-berri
mateo-berri merged commit 07aeaa1 into litellm_internal_staging Jul 8, 2026
128 checks passed
@mateo-berri
mateo-berri deleted the litellm_fix_merge_query_params_clobber branch July 8, 2026 01:56
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