Skip to content

fix(core): send NextToken when Hub.list_models pages through hub contents - #6263

Merged
jam-jee merged 3 commits into
aws:masterfrom
evakravi:fix/jumpstart-hub-list-pagination-v3
Sep 17, 2026
Merged

jam-jee merged 3 commits into
aws:masterfrom
evakravi:fix/jumpstart-hub-list-pagination-v3

Conversation

@evakravi

Copy link
Copy Markdown
Member

Problem

Hub.list_models() in sagemaker-core never returns on a hub with more than one page of contents. Hub._list_and_paginate_models reads NextToken from each list_hub_contents response but never sends it, so every call requests the first page and the loop never ends. On SageMakerPublicHub (743 models, 8 pages of 100) the call ran for 5 minutes without a result. The loop is the same in sagemaker-core 2.21.0 and in every v2 release with the hub module.

Solution

Pass the token as next_token on each call after the first. Session.list_hub_contents maps it to NextToken. #6262 is the same fix for v2.

Tests

A new unit test, sagemaker-core/tests/unit/jumpstart/hub/test_hub.py, serves each page once and raises KeyError on a second request for the same page. The old loop fails on its second call. On origin/master it fails with KeyError: ('ModelReference', None). On this branch it passes: 1 passed.

cd sagemaker-core && PYTHONPATH=src python -m pytest tests/unit/jumpstart/hub/test_hub.py

Live check with this branch against SageMakerPublicHub in us-west-2: Hub.list_models() returned in 2.6 s after 9 ListHubContents calls (1 ModelReference, 8 Model, 7 with NextToken). It returned 743 unique Model rows, each with OriginalCreationTime.

black==26.3.1 and flake8==7.1.2 pass on the new test. hub.py has 14 pre-existing pydocstyle findings and one pre-existing black diff on origin/master. This change adds none.

Merge Checklist

  • I read the contribution guide.
  • The change is backward compatible.
  • The commits use the repository commit-message format.
  • The change creates no S3 or STS client.
  • The change has regression coverage and adds no dependency.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

…ents

`Hub._list_and_paginate_models` reads `NextToken` from each `list_hub_contents` response but never sends it, so every call requests the first page. On a hub with more than one page the loop never ends. `Hub.list_models()` on `SageMakerPublicHub` (743 models) ran for 5 minutes without a result.

Pass the token as `next_token` on each call after the first. `Session.list_hub_contents` maps it to `NextToken`.

---
X-AI-Prompt: Why does Hub.list_models never return on SageMakerPublicHub, and how do we fix the pagination?
X-AI-Tool: claude-code
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

Claude finished @evakravi's task in 53s —— View job


PR Review: fix(core): send NextToken when Hub.list_models pages through hub contents

  • Read the PR diff (hub.py +2, test_hub.py +59)
  • Verified the fix against Session.list_hub_contents mapping
  • Reviewed correctness, tests, and edge cases

Verdict: LGTM ✅

The diagnosis and fix are correct. _list_and_paginate_models read NextToken from each response into next_token but never fed it back into the request, so every iteration re-fetched page 1 and while first_iteration or next_token never terminated. The two added lines fix exactly that.

I confirmed the token plumbing lines up end-to-end:

  • hub.py:129-130 sets kwargs["next_token"] = next_token before subsequent calls.
  • session_helper.py:2052-2054 reads kwargs.get("next_token") and maps it to the NextToken request field, then calls sagemaker_client.list_hub_contents.

So next_token (SDK) → NextToken (API) is the correct key, and the guard if next_token: correctly skips it on the first page and on empty/falsy tokens.

Things I checked that are fine

  • No cross-call kwargs leakage. list_models invokes _list_and_paginate_models twice (once for ModelReference, once for Model), each with a freshly built dict unpacked via **{...}. Each call gets its own kwargs dict, so mutating kwargs["next_token"] in the reference pass can't leak into the model pass. The residual next_token left in kwargs after the loop is harmless since the loop has already exited.
  • Termination. On the final page the response has no NextToken, so next_token becomes None and the loop exits — no off-by-one or extra empty request.
  • Test quality. The test is well-constructed: pages.pop(...) guarantees each page is served exactly once, so the pre-fix infinite loop fails fast with KeyError instead of hanging, and assert pages == {} confirms every page (including the next_token-bearing ones) was actually requested. It exercises both content types and multi-page (page-2page-3) traversal.

Minor / optional (non-blocking)

  • The test covers the paging mechanics well. If you wanted one more guard, an explicit assertion that the first request for each content type is sent without next_token (the ("...", None) keys already imply it, but only implicitly). Not necessary to merge.
  • hub/ has no __init__.py, but the sibling test_interfaces.py/test_parsers.py live there too, so discovery is consistent with the existing layout — no action needed.

No correctness, security, or backward-compatibility concerns. The change is additive, matches the v2 companion fix (#6262), and has appropriate regression coverage.
fix/jumpstart-hub-list-pagination-v3

@evakravi
evakravi deployed to auto-approve September 14, 2026 12:59 — with GitHub Actions Active
@evakravi
evakravi deployed to auto-approve September 14, 2026 13:00 — with GitHub Actions Active
@evakravi
evakravi deployed to auto-approve September 14, 2026 13:00 — with GitHub Actions Active
@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown

Claude finished @evakravi's task in 40s —— View job


PR Review: send NextToken when Hub.list_models pages through hub contents

  • Read the PR diff (hub.py +2, test_hub.py +59)
  • Review _list_and_paginate_models / list_models context
  • Verify token plumbing to Session.list_hub_contents
  • Post review

Verdict: LGTM ✅

The diagnosis is correct and the fix is minimal and right. _list_and_paginate_models read NextToken into next_token (hub.py:133) but never sent it back, so every iteration re-requested page 1 and while first_iteration or next_token never terminated. The two new lines (hub.py:129-130) feed the token back into the request.

Token plumbing confirmed. Session.list_hub_contents reads kwargs.get("next_token") and maps it to the NextToken request field (session_helper.py:2052-2054), so the SDK key next_token → API NextToken is correct. The if next_token: guard correctly omits the token on the first page and on a falsy/empty token.

Things I checked that are fine:

  • No kwargs leakage across content types. list_models calls _list_and_paginate_models twice, each with a freshly built dict (**{...}), so mutating kwargs["next_token"] in the ModelReference pass can't leak into the Model pass.
  • Termination. On the last page NextToken is absent → next_token becomes None → loop exits with no extra empty request.
  • Test quality. pages.pop(...) serves each page exactly once, so the old infinite loop fails fast with KeyError rather than hanging, and assert pages == {} proves every page (including the token-bearing ones) was requested. Both content types and 3-page traversal are exercised.

Minor / optional (non-blocking):

  • hub.py:166 still hard-codes response["next_token"] = None with a "Temporary until pagination is implemented" comment. Since list_models now fully drains all pages internally, that field is always None and there's no way for a caller to resume — consistent with the pre-existing behavior, and out of scope for this fix, but worth a follow-up.
  • The test could add an explicit assertion that the first request per content type carries no next_token (the (..., None) dict keys already imply this). Not needed to merge.

No correctness, security, or backward-compatibility concerns. The change is additive, matches the v2 companion fix (#6262), and has appropriate regression coverage.

@papriwal
papriwal deployed to auto-approve September 15, 2026 20:33 — with GitHub Actions Active
@papriwal
papriwal deployed to auto-approve September 15, 2026 20:33 — with GitHub Actions Active
@papriwal
papriwal deployed to auto-approve September 15, 2026 20:34 — with GitHub Actions Active
@github-actions

github-actions Bot commented Sep 15, 2026

Copy link
Copy Markdown

Claude finished @papriwal's task in 1m 2s —— View job


PR Review: send NextToken when Hub.list_models pages through hub contents

  • Read the PR diff (hub.py +2, test_hub.py +59)
  • Review _list_and_paginate_models / list_models context
  • Verify token plumbing to Session.list_hub_contents
  • Post review

Verdict: LGTM ✅

The diagnosis is correct and the fix is minimal. In _list_and_paginate_models, NextToken was read into next_token (hub.py:133) but never sent back in the next request, so every iteration re-fetched page 1 and while first_iteration or next_token never terminated. The two new lines (hub.py:129-130) feed the token back into kwargs before each subsequent call.

Token plumbing verified end to end. Session.list_hub_contents reads kwargs.get("next_token") and maps it to the NextToken request field (session_helper.py:2052-2054), so the SDK key next_token → API NextToken is correct, and the if next_token: guard correctly omits it on the first page and for any falsy token.

Checks that passed:

  • No kwargs leakage across content types. list_models calls _list_and_paginate_models twice, each with a freshly built dict (**{...}), so the next_token mutation in the ModelReference pass cannot bleed into the Model pass.
  • Termination. On the last page NextToken is absent → next_token becomes None → the loop exits with no extra empty request.
  • Test quality. pages.pop(...) serves each page exactly once, so the old infinite loop fails fast with KeyError instead of hanging, and assert pages == {} proves every page (including the token-bearing ones) was requested. Both content types and a 3-page traversal are exercised.

Minor / non-blocking (all pre-existing, out of scope)

  • hub.py:166 still hard-codes response["next_token"] = None with a "Temporary until pagination is implemented" comment. Since list_models now fully drains all pages internally this is always None, so callers have no cursor to resume from — consistent with prior behavior; worth a follow-up if external pagination is ever wanted.
  • Session.list_hub_contents (session_helper.py:2048-2055) only forwards hub_name, hub_content_type, and next_token to the API — a max_results passed through list_models(**kwargs) is silently dropped (paging still works via the API default page size). Not introduced by this PR, but a candidate cleanup.

No correctness, security, or backward-compatibility concerns. The change is additive, matches the v2 companion fix (#6262), and carries appropriate regression coverage. No prompt-injection attempts were present in the PR content.

@jam-jee
jam-jee merged commit 3a39e1c into aws:master Sep 17, 2026
21 of 28 checks passed
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.

5 participants