{"record":{"id":"0b016fa0609644f2","repo":"larksuite/cli","slug":"pagination-exhausted-its-page-budget-without-produ","errorCode":null,"errorMessage":"pagination exhausted its page budget without producing a terminal result","messagePattern":"pagination exhausted its page budget without producing a terminal result","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pagination/walk.go","lineNumber":122,"sourceCode":"\t\t\treturn state, &CursorError{Kind: CursorMissing, Page: pageNumber}\n\t\t}\n\t\tif _, duplicate := seen[nextToken]; duplicate {\n\t\t\treturn state, &CursorError{Kind: CursorRepeated, Page: pageNumber, Token: nextToken}\n\t\t}\n\t\tstate.NextToken = nextToken\n\t\tif pageNumber == options.MaxPages {\n\t\t\treturn state, nil\n\t\t}\n\t\tseen[nextToken] = struct{}{}\n\t\ttoken = nextToken\n\t\tif options.Delay > 0 {\n\t\t\tif err := wait(ctx, options.Delay); err != nil {\n\t\t\t\treturn state, &WaitError{Err: err}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn state, fmt.Errorf(\"pagination exhausted its page budget without producing a terminal result\")\n}\n\n// WaitContext waits for one inter-page delay and observes cancellation.\nfunc WaitContext(ctx context.Context, delay time.Duration) error {\n\tif delay <= 0 {\n\t\treturn nil\n\t}\n\ttimer := time.NewTimer(delay)\n\tdefer timer.Stop()\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn ctx.Err()\n\tcase <-timer.C:\n\t\treturn nil\n\t}\n}\n","sourceCodeStart":104,"sourceCodeEnd":139,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/pagination/walk.go#L104-L139","documentation":"Walk pages through a cursor-based API and stops when it exhausts the configured page budget without receiving a terminal result; returning this error prevents infinite pagination loops against APIs that never terminate or always return a next cursor. It signals a budget/policy failure, not a network error.","triggerScenarios":"Calling Walk with a page budget (max pages) that is fully consumed while the API keeps returning a resume/cursor with no terminal state — e.g. too-small budget for a large dataset, or a server returning the same cursor repeatedly.","commonSituations":"Listing a dataset far larger than the default page budget; a misbehaving/stale API that keeps returning a next cursor; resume-token loops after API version changes; Delay+ctx waits consuming the walk window.","solutions":["Increase the pagination page budget to cover the dataset size.","Verify the API is actually advancing cursors — log page tokens; a repeating cursor indicates a server/bug issue.","Resume from the last state's resume token in a follow-up Walk instead of doing it in one run.","Check pagination/version behavior of the upstream API for known cursor loops."],"exampleFix":"// before\nstate, err := pagination.Walk(ctx, opts) // budget too small\n// after\nopts.PageBudget = 10000 // sized to dataset\nstate, err := pagination.Walk(ctx, opts)\nif err != nil {\n    // persist state.ResumeToken and continue later\n}","handlingStrategy":"validation","validationCode":"if expectedPages > opts.PageBudget {\n    return errors.New(\"dataset larger than pagination budget; raise PageBudget or use resume token\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Size the page budget to the dataset before walking","Persist state.ResumeToken so long walks can be resumed across runs","Log page cursors to detect non-advancing server cursors early"],"tags":["pagination","api","loop-prevention"],"backgroundTag":"pagination-budget-exhausted","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}