Skip to content

fix(pagination): continue after empty cursor pages - #893

Open
tvost2 wants to merge 3 commits into
openai:mainfrom
tvost2:fix/cursor-pagination-empty-pages
Open

fix(pagination): continue after empty cursor pages#893
tvost2 wants to merge 3 commits into
openai:mainfrom
tvost2:fix/cursor-pagination-empty-pages

Conversation

@tvost2

@tvost2 tvost2 commented Sep 4, 2026

Copy link
Copy Markdown

Fixes #890

NextCursorPage stopped pagination whenever a page had an empty data array, even when has_more was true and the server supplied a next cursor. Allow GetNextPage and the auto-pager to traverse empty intermediate pages, and add a regression test using the public Admin Organization Groups endpoint.

Validation:

  • go test -race . -run 'TestNextCursorPaginationContinuesAfterEmptyPage|TestPaginationPreservesCustomHTTPClient' -count=1
  • go test ./packages/pagination
  • git diff --check

@tvost2
tvost2 requested a review from a team as a code owner September 4, 2026 21:39

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7cb9aac2c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 330 to 331
func (r *NextCursorPage[T]) GetNextPage() (res *NextCursorPage[T], err error) {
if len(r.Data) == 0 {
return nil, nil
}

if r.JSON.HasMore.Valid() && r.HasMore == false {

@chatgpt-codex-connector chatgpt-codex-connector Bot Sep 4, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Implement the fix in the Castiron generator

This changes Castiron-owned shared pagination scaffolding, but the commit contains neither a generator change nor regenerated metadata, leaving the underlying template to keep emitting the faulty empty-page guard for future generations and sibling SDKs. Fix the shared Castiron template and regenerate this file rather than carrying only a repository-local patch.

AGENTS.md reference: AGENTS.md:L68-L70

Useful? React with 👍 / 👎.

Comment on lines 386 to +388
r.idx = 0
r.page, r.err = r.page.GetNextPage()
if r.err != nil || r.page == nil || len(r.page.Data) == 0 {
if r.err != nil {

@chatgpt-codex-connector chatgpt-codex-connector Bot Sep 4, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop when empty pages repeat a cursor

When an empty page returns has_more: true and repeats the same nonempty next cursor, this loop issues the identical request indefinitely inside a single Next() call; with a successful server and a context without a deadline, the caller never regains control and the client can continuously hammer the endpoint. Detect a cursor that makes no progress and terminate with an error, with a public-entrypoint regression test covering the malformed response.

AGENTS.md reference: AGENTS.md:L38-L40

Useful? React with 👍 / 👎.

@tvost2

tvost2 commented Sep 4, 2026

Copy link
Copy Markdown
Author

Addressed the repeated-cursor feedback in commit a0afa6b: the auto-pager now returns an error instead of issuing the same request indefinitely, with a public HTTP regression test. The pagination implementation is Castiron-generated; this checkout does not contain the Castiron generator/template source or regeneration metadata, so I cannot make the requested upstream generator edit from this repository. The generated scaffolding change remains isolated and the focused -race tests pass.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a0afa6bbad

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/pagination/pagination.go Outdated
Comment on lines +388 to +390
if r.page.Next != "" && r.page.Next == r.lastCursor {
r.err = errors.New("pagination cursor did not advance")
return false

@chatgpt-codex-connector chatgpt-codex-connector Bot Sep 4, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check has_more before treating a cursor as repeated

When the final page explicitly returns has_more:false but leaves next set to the cursor used to fetch it, this branch runs after yielding that page's items and before GetNextPage() can apply its terminal has_more check. A subsequent Next() therefore reports pagination cursor did not advance instead of clean exhaustion, regressing the existing explicit-false behavior; short-circuit terminal pages before this comparison and cover this response shape in the regression test.

AGENTS.md reference: AGENTS.md:L71-L73

Useful? React with 👍 / 👎.

Comment thread packages/pagination/pagination.go Outdated
Comment on lines +388 to +392
if r.page.Next != "" && r.page.Next == r.lastCursor {
r.err = errors.New("pagination cursor did not advance")
return false
}
r.lastCursor = r.page.Next

@chatgpt-codex-connector chatgpt-codex-connector Bot Sep 4, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Track all requested cursors when detecting cycles

When empty pages advertise a cursor cycle longer than one token—for example A, then B, then A—each adjacent token differs, so a single Next() loops indefinitely issuing alternating requests if its context has no deadline. Fresh evidence beyond the prior repeated-cursor comment is that the new implementation retains only lastCursor; track all cursors already requested and fail before reusing any of them, with an A → B → A public-entrypoint regression test.

AGENTS.md reference: AGENTS.md:L38-L40

Useful? React with 👍 / 👎.

@tvost2

tvost2 commented Sep 5, 2026

Copy link
Copy Markdown
Author

Addressed the remaining pagination review feedback in the public NextCursorPage autopager:

  • explicit has_more:false now terminates before cursor-reuse checks;
  • all previously requested cursors are tracked, so multi-token cycles such as A -> B -> A terminate with an error;
  • added public HTTP regressions for both cases.

Validation: go test . -run TestNextCursorPagination -count=1 passes. The Castiron generator/template source is not present in this checkout, so the generated shared scaffolding change remains limited to the repository output as noted in the earlier review response.

@tvost2

tvost2 commented Sep 5, 2026

Copy link
Copy Markdown
Author

Correction: the commit was created locally first, but the initial push used the read-only upstream remote and returned 403. It has now been pushed successfully to the fork branch; the PR should update from the new commit.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0f1559cdd2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

err: err,
page: page,
err: err,
seenCursors: make(map[string]struct{}),

@chatgpt-codex-connector chatgpt-codex-connector Bot Sep 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Seed cycle detection with the initial cursor

When ListAutoPaging starts with a nonempty After value, this map does not include the cursor used for the initial request. If that response has data, has_more:true, and echoes the same next cursor, the pager returns the first page's items, makes the identical request, and returns those items again before detecting the repetition on a later Next() call. Seed cycle detection from the initial request cursor, or track the cursor used to fetch each page, and add a public-entrypoint regression test for this resume-pagination case.

AGENTS.md reference: AGENTS.md:L71-L73

Useful? React with 👍 / 👎.

r.err = errors.New("pagination cursor did not advance")
return false
}
r.seenCursors[r.page.Next] = struct{}{}

@chatgpt-codex-connector chatgpt-codex-connector Bot Sep 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound cursor history instead of retaining every page cursor

On a successful pagination with unique cursors, every exhausted page adds another string to seenCursors, and nothing removes entries until the entire pager is discarded. This changes auto-pagination from constant auxiliary memory to memory proportional to the total page count; for example, iterating a large group or user collection with a small limit can retain millions of opaque cursors despite processing items incrementally. Restrict history to the consecutive empty-page traversal or use constant-space cycle detection so normal large iterations do not accumulate all prior cursors.

AGENTS.md reference: AGENTS.md:L197-L198

Useful? React with 👍 / 👎.

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.

NextCursorPage auto-pagination stops on empty pages with a next cursor

1 participant