You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Pagination cycle detector only catches adjacent cursor repeats #352
The pagination cycle guard in _list_all only stores the most recent cursor. It catches the realistic last-page-replayed shape (A → A → A → …) but misses any cycle of length ≥ 2. A pagination cache invalidation that rotates A → B → A → B → …, or a load-balancer randomly returning one of two stale cursors from two backend pods, would loop forever — or until max_pages / total_timeout saves the caller, neither of which is set by default on most list_all callers.
The docstring even claims this guard handles "server-side pagination bugs." Short cycles ARE server-side pagination bugs and aren't caught.
Evidence
kalshi/resources/_base.py:343-371 (sync path):
last_cursor: str|None=Nonepages_fetched=0whilemax_pagesisNoneorpages_fetched<max_pages:
page=self._list(...)
yieldfrompage.itemspages_fetched+=1ifnotpage.cursor:
breakifpage.cursor==last_cursor:
raiseKalshiError(
f"Cursor loop detected on {path}: server returned "f"cursor {page.cursor!r} which was already used. "f"This indicates a server-side pagination bug."
)
last_cursor=page.cursorcurrent_params["cursor"] =page.cursor
kalshi/resources/_base.py:587 — identical pattern in the async _list_all (AsyncResource).
Suggested fix
Replace last_cursor: str | None with seen: set[str] = set() on both sync and async paths. On each new cursor, if page.cursor in seen: raise KalshiError(...). Memory is O(pages) — typically hundreds, trivial for any production workload — and the cost of detecting real short-cycle bugs is well worth it. Update the docstring (currently advertises "O(1) regardless of page count") to match. Add a regression test in tests/test_base_helpers.py using a respx mock that cycles A → B → A.
Activity
polish(transport): async close cancellation-safe + _delete_with_body …