fix: Stop the paginator when a page cursor repeats - #641
Open
razor-x wants to merge 2 commits into
Open
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y1RzepycXEYA3LStfjt8cY
razor-x
changed the base branch from
main
to
claude/python-sdk-audit-o2iid9-07-paginator-hooks
August 28, 2026 00:15
… claude/python-sdk-audit-o2iid9-08-cursor-guard
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Neither paginator had any loop guard: a server regression or proxy-cached page that repeats a
next_page_cursorloopedflatten()forever and grewflatten_to_list()until OOM — the per-request timeout resets every page, so nothing else stops the process (SDK audit finding M6; ports JS #1006 / PHP #466).flatten/flatten_to_list× sync / async), is centralized in one private_walk()generator per paginator — the same single-walker shape as the JS fix, so the guard cannot be missing from one of the copies._walk()keeps a seen-cursor set and stops silently when the server hands back a cursor it has already followed.Stacked on #640; diff shrinks as the stack merges.
Testing
New tests (sync
flatten_to_list, syncflatten, async) against a recording server that pins one cursor: the paginator fetches the first page, follows the cursor once, sees it repeat, and stops — request count pinned at exactly 2, per the JS-wave lesson that loop-guard tests must count requests, not just observe termination.Revert check: with the previous paginator restored, the test loops until killed by a 15s timeout — the audit's infinite-loop symptom.
Full suite: 235 passed; mypy, pylint (10.00), black clean.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Y1RzepycXEYA3LStfjt8cY
Generated by Claude Code