Repository navigation
fix(api): Keep per_page stable when listPage walks offset cursors - #130518
Closed
sentry-junior[bot] wants to merge 1 commit into
Closed
sentry-junior[bot] wants to merge 1 commit into
sentry-junior[bot] wants to merge 1 commit into
Conversation
sentry-junior
Bot
force-pushed
the
fix/api-list-page-offset
branch
from
November 12, 2026 21:36
f2629b2 to
f0a7b4b
Compare
Contributor
Sentry Snapshot Testing
|
sentry-junior
Bot
force-pushed
the
fix/api-list-page-offset
branch
from
November 13, 2026 07:24
f0a7b4b to
3332485
Compare
listPage asked each later page for only the rows still missing. Sentry's OffsetPaginator reads a cursor's window with the page size the cursor encodes, so pairing 100:1:0 with per_page=50 returned rows 150-199 and dropped 100-149. listProjects, listTeams and listOrganizations lost rows whenever limit was above 100. listPage now walks through paginate, which keeps one page size per walk, reuses the size an offset cursor encodes, and returns a cursor that resumes mid-page. Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
sentry-junior
Bot
force-pushed
the
fix/api-list-page-offset
branch
from
November 13, 2026 07:32
3332485 to
6a2d11f
Compare
Member
|
Can you move this and the next on stack back to sentry-api-schema repo for now as it advanced a lot and it's gonna be easier to get docs aligned first and then move |
Contributor
Author
|
Moved to getsentry/sentry-api-schema#149. |
BYK
added a commit
to getsentry/sentry-api-schema
that referenced
this pull request
Nov 13, 2026
Ported from getsentry/sentry#130518, per @BYK's request there: get the docs aligned here first, then move `@sentry/api` into getsentry/sentry. The commit applied cleanly on top of 2.0.0. Cursor Bugbot found the bug while reviewing the move ([thread](getsentry/sentry#130472 (comment))). It ships in the published `@sentry/api` 2.0.0. ## Bug `listPage` asked each later page for only the rows still missing: `per_page = min(100, limit - items.length)`. `listProjects`, `listTeams` and `listOrganizations` are served by Sentry's `OffsetPaginator`. Its cursor is `<page size>:<page>:<is_prev>`. When `per_page` doesn't match the cursor's page size, it reads the cursor's window and keeps only the last `per_page` rows (`src/sentry/api/paginator.py` in getsentry/sentry). So `100:1:0` with `per_page=50` returns rows 150–199, and rows 100–149 are lost. Any `limit` above 100 dropped rows without an error. ## Fix `listPage` now walks through the package's existing `paginate`, which: - uses one `per_page` for the whole walk. A fresh walk splits `limit` evenly: 150 becomes 2 × 75. - reuses the page size that an offset cursor encodes when resuming, whatever the new `limit` is. - returns a cursor that resumes at the first row not returned, including partway through a page. `listPage` still validates each page with the schema, and still rejects a page with more rows than requested or a repeated cursor. One behavior changes: if a walk reaches `MAX_LIST_PAGES`, it now returns the rows it has and a resume cursor instead of throwing. Before this change, a stable page size couldn't hit that cap in a normal walk. Now a resumed small-page cursor can, for example `25:1:0` with `limit: 5000`. ## Tests - `test/api-client/core.test.ts` models `OffsetPaginator`. It confirms that `100:1:0` with `per_page=50` reads rows 150–199. Then it checks two things: - a 150-row walk and its resume return rows 0–299 in order, all with `per_page=75`; - resuming `100:1:0` with `limit: 50` returns rows 100–149, then 150–199, both requested with `per_page=100`. - The `listOrganizations` test now expects `per_page` to stay at 3 on the second request. It was 1, which was the bug. `pnpm build`, `pnpm typecheck` and `pnpm test` pass locally (53 files, 675 tests). No UI change. Co-Authored-By: BYK <126780+BYK@users.noreply.github.com> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3A69da19b385017e68b7f9eace56e9477f --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3A69da19b385017e68b7f9eace56e9477f) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3A69da19b385017e68b7f9eace56e9477f/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <126780+BYK@users.noreply.github.com>
This branch was successfully deployed
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.
Stacked on #130472. This fixes the pagination bug Cursor Bugbot found during that review (thread). The bug also ships in the published
@sentry/api1.54.0.Bug
listPageasked each later page for only the rows still missing:per_page = min(100, limit - items.length).listProjects,listTeamsandlistOrganizationsare served by Sentry'sOffsetPaginator. Its cursor is<page size>:<page>:<is_prev>, and whenper_pagedoesn't match the cursor's page size, it reads the cursor's window and keeps only the lastper_pagerows (src/sentry/api/paginator.py). So100:1:0withper_page=50returns rows 150–199, and rows 100–149 are lost. Anylimitabove 100 dropped rows without an error.Fix
listPagenow walks through the package's existingpaginate, which:per_pagefor the whole walk. A fresh walk splitslimitevenly: 150 becomes 2 × 75.limitis.listPagestill validates each page with the schema and still rejects a page with more rows than requested or a repeated cursor. One behavior changes: if a walk reachesMAX_LIST_PAGES, it now returns the rows it has and a resume cursor instead of throwing. Before this change, a stable page size couldn't hit that cap in a normal walk. Now a resumed small-page cursor can, for example25:1:0withlimit: 5000.Tests
OffsetPaginatorincore.test.ts. It confirms that100:1:0withper_page=50reads rows 150–199. Then it checks two things:per_page=75;100:1:0withlimit: 50returns rows 100–149, then 150–199, both requested withper_page=100.listOrganizationstest now expectsper_pageto stay at 3 on the second request. It was 1, which was the bug.Package build, typecheck and tests pass (657 tests).
No UI change.
via @BYK.
--
View Session in Sentry