Skip to content

fix(proxy): report has_more false on caller-scoped file list pages - #36326

Merged
mateo-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_files_list_has_more_scoped
Aug 9, 2026
Merged

fix(proxy): report has_more false on caller-scoped file list pages#36326
mateo-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_files_list_has_more_scoped

Conversation

@mateo-berri

@mateo-berri mateo-berri commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Scoped file lists kept the upstream has_more: true
  • No next page is reachable, so SDK auto-pagination loops forever

How it solves it:

  • Caller-scoped list pages now always report has_more: false
  • Auto-pagination completes after the single reachable page

User Flow

Before: a user listing their files through the proxy with an auto-paginating OpenAI SDK client gets stuck receiving the same page of their own files forever

  1. The admin mints a virtual key with POST https://litellm-domain/key/generate
  2. The user uploads batch files with POST https://litellm-domain/v1/files and gets managed file ids back
  3. The user lists files with GET https://litellm-domain/v1/files and receives their files with "has_more": true, inherited from the shared upstream account
  4. Their SDK auto-paginates with GET https://litellm-domain/v1/files?after=<last_id>, which returns the identical page with "has_more": true again
  5. The iteration never completes, and the user sees endless duplicates of their own files until they kill the process

After: the same listing finishes cleanly after one page

  1. The admin mints a virtual key with POST https://litellm-domain/key/generate
  2. The user uploads batch files with POST https://litellm-domain/v1/files and gets managed file ids back
  3. The user lists files with GET https://litellm-domain/v1/files and receives their files with "has_more": false
  4. The SDK stops after that single page and the iteration completes normally, each file appearing exactly once

Relevant issues

Fixes #36324

Linear ticket

Pre-Submission checklist

  • 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

Two live proxies against real api.openai.com (real uploads, real account, no mocks), each in its own worktree with its own venv, its own empty postgres database, and its own port. Both legs drive the exact User Flow above with the OpenAI python SDK as the end-user client (base_url pointed at the proxy, api_key a freshly minted non-admin virtual key): mint the key via POST /key/generate, upload two batch .jsonl files via POST /v1/files with target_model_names=gpt-5.4-nano, list, then auto-paginate. The loop precondition held on both legs: a direct control GET https://api.openai.com/v1/files returned 5444 entries with has_more: true

Before, at 1f01f19457 (the merge base, which already contains #36093)

The owner's uploads minted file-8pLa6rioZ1XC1BKWb7TE9j and file-4eWQrLbGPbUp2Cz2ZKpT6r upstream, confirmed newest on the account by a direct control list. The owner's list through the proxy keeps the upstream has_more (2.99s wall, so the upstream page was really fetched and scoped):

$ curl -sS http://localhost:57219/v1/files -H "Authorization: Bearer $OWNER_KEY"
data: the owner's 2 managed ids (bGl0ZWxsbV9wcm94eTph..., distinct in full),
has_more: true, first_id/last_id matching data[0]/data[-1]

Following the cursor returns the identical page:

$ curl -sS "http://localhost:57219/v1/files?after=$LAST_ID" -H "Authorization: Bearer $OWNER_KEY"
byte-for-byte identical to the previous list (cmp confirmed), has_more: true again

The real OpenAI python SDK auto-paginating over it never terminates:

# for f in client.files.list(): ...  with a 10-item safety cap, page fetches counted
CAP OF 10 YIELDED ITEMS HIT -> auto-pagination did NOT terminate
items yielded: 10
page fetches (HTTP GETs to /files): 5
  fetch 1: http://localhost:57219/v1/files
  fetch 2..5: http://localhost:57219/v1/files?after=bGl0ZWxsbV9wcm94eTph... (same cursor every round)

The SDK refetched the same 2-item page five times, yielding the owner's 2 files over and over until the cap: the endless-duplicates loop from #36324

After, at 82662dc104 (this branch's tip)

Same flow, fresh uploads (file-TxYAoJf9KEsqSxyN3DaiRH, file-9SFcxmSteqbL6M9Ah6SsJC upstream). The direct upstream control still reported has_more: true with the owner's fresh upload as its first_id, and the scoped page now reports false (3.7s wall):

$ curl -sS http://localhost:52731/v1/files -H "Authorization: Bearer $OWNER_KEY"
n: 2, has_more: false, first_id/last_id: managed ids matching data[0]/data[-1]

The same SDK script completes naturally:

HTTP fetch #1: GET http://localhost:52731/v1/files
yielded item #1: bGl0ZWxsbV9wcm94eTph... (owner managed id)
yielded item #2: bGl0ZWxsbV9wcm94eTph... (owner managed id)
COMPLETED NATURALLY: 2 items yielded (2 unique) across 1 page fetch(es)

The #36093 empty-page behavior is intact, checked with a second key on a different user_id and team:

$ curl -sS http://localhost:52731/v1/files -H "Authorization: Bearer $OTHER_KEY"
{"data":[],"has_more":false,"object":"list","first_id":null,"last_id":null}

Both legs deleted their uploads through the proxy afterwards and verified 404 on direct retrieves straight from api.openai.com, leaving the real account as found

Two observations from the runs, both pre-existing and left alone by this PR: DELETE /v1/files/{managed_id} through the proxy responds with the managed file-object shape carrying "deleted": null rather than OpenAI's {"deleted": true} confirmation shape even though the upstream delete really happens, and the shared QA account reports has_more: true on a full direct list of 5444 entries, so the loop precondition is the account's steady state rather than an over-10,000-files edge case

Type

🐛 Bug Fix

Changes

The cursor-scoping applied to file list pages, added in #36093, cleared has_more only when the caller's page came back empty, so a non-empty scoped page kept whatever the shared upstream account reported. Since the after parameter is never forwarded upstream, no later page is ever reachable through the proxy, and has_more: true sent OpenAI SDK auto-pagination into an endless loop over the identical page. The scoping now clears has_more on every page it touches

The owner-page regression test now feeds an upstream page with has_more: true and asserts the scoped page reports false; it fails without the one-line change

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

@greptile-apps

greptile-apps Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR corrects pagination metadata for caller-scoped managed-file listings so SDK auto-pagination terminates after the only reachable page.

  • Always sets has_more to false after caller-level file filtering.
  • Updates cursor-scoping documentation to explain the single-page behavior.
  • Extends the regression test to cover a non-empty upstream page that originally reports has_more: true.

Confidence Score: 5/5

The PR appears safe to merge and correctly prevents auto-pagination from repeatedly requesting the same caller-scoped page.

The changed metadata now reflects that no subsequent page is reachable through the proxy, while preserving the scoped data and rebuilt cursor identifiers; the regression test covers the previously failing non-empty-page case.

Important Files Changed

Filename Overview
enterprise/litellm_enterprise/proxy/hooks/managed_files.py The scoped-page cursor helper now consistently clears has_more, matching the fact that pagination cursors are not forwarded upstream.
tests/enterprise/litellm_enterprise/proxy/hooks/test_managed_files.py The existing owner-scoping test now verifies that an upstream has_more: true value is cleared on a non-empty scoped page.

Reviews (1): Last reviewed commit: "fix(proxy): report has_more false on cal..." | Re-trigger Greptile

@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@mateo-berri
mateo-berri merged commit e9d1ea5 into litellm_internal_staging Aug 9, 2026
79 checks passed
@mateo-berri
mateo-berri deleted the litellm_files_list_has_more_scoped branch August 9, 2026 00: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.

[Bug]: File list passes upstream has_more through on scoped pages, so SDK auto-pagination loops forever

2 participants