generated client treated the opaque cursor as a page number - requests came back 400 invalid cursor
A playbook for opaque-cursor handling: pass the cursor through verbatim, never parse, decode, or renumber it. Use when generated code treats the cursor as a page number and the provider 400s with invalid cursor. Not for expired cursors, double-encoding, or endpoints that genuinely use page numbers.
TL;DR
An opaque cursor is a token, not a number. Take the exact string the provider returned and send it back unchanged - no parsing it as an integer, no incrementing, no decoding. The 400 invalid cursor disappears the moment the client stops interpreting a value it was never meant to read.
The query
generated client treated the opaque cursor as a page number - requests came back 400 invalid cursorSteps
1. Find where the client interprets the cursor
Search the generated pagination code for any operation on the cursor value: integer conversion, arithmetic, string splitting, base64 decoding, or formatting it into a page-number parameter. That line is the bug.
Expected: one identified location where the cursor is transformed instead of passed through.
2. Replace interpretation with pass-through
Store the cursor as an opaque string and send it back in exactly the parameter the provider documents. No int() conversion, no + 1, no decoding. If the provider names the parameter cursor, the client sends cursor with the untouched string.
Expected: pagination code with zero transformations applied to the cursor value.
3. Fix the initial-page call
The first page usually takes no cursor or an explicit empty value. Make sure the client does not invent a cursor of 0 or 1 for the first request - that is the same bug in a different place.
Expected: the first request sends no cursor (or the provider's documented initial value), subsequent requests echo the returned cursor.
4. Re-run the full pagination and watch for 400s
Paginate a real list end to end and confirm no 400 invalid cursor responses. Log the cursor on each request during the test so any future transformation is visible.
Expected: a complete paginated fetch with zero invalid-cursor errors.
5. Type the cursor as opaque in the generated code
Declare the cursor as a string type with a comment marking it opaque and provider-defined. The next regeneration sees the annotation and does not "helpfully" convert it to a number.
Expected: a type annotation plus comment that survives regeneration.
Use this when
- The provider 400s with "invalid cursor" on page two onward
- The generated code converts the cursor to an integer or increments it
- The first page works and every subsequent page fails
- Cursor values in logs look truncated, rounded, or renumbered
Not for this skill when
- The cursor is expired server-side (time-based invalidation, different fix)
- The cursor is double-URL-encoded (encoding problem)
- The endpoint genuinely uses numeric page parameters (then page numbers are correct)
- The 400 names a different parameter (read the error body carefully)
Variant phrasings
400 invalid cursor on the second page
The first page needed no cursor, so the bug only shows on page two. Step 1 finds the transformation; step 2 removes it.
cursor looks like a number so the client parsed it as one
Some opaque cursors are numeric-looking strings. Looks-like-a-number is not is-a-number: step 5's opaque-string typing prevents the next agent from making the same inference.
Why it happens
Cursors that look like 1683749200 invite arithmetic, and the agent's training data is full of offset pagination where page numbers increment. The agent pattern-matched "thing that changes per page" to "page number" and generated increment logic. The provider's cursor is an internal bookmark - possibly encrypted or hashed - and any modification invalidates it. The 400 is the provider saying "I never issued this value."
Edge cases
- Provider documents the cursor as base64: still do not decode it. "Base64-encoded" describes the transport, not an invitation to interpret the contents.
- Cursor contains characters needing URL encoding: encode it once for transport (that is not interpretation), and never double-encode.
- Mixed pagination styles across endpoints: type each endpoint's cursor separately. One endpoint's page numbers do not make another endpoint's cursor numeric.
- Client caches cursors across runs: persist the exact string. A cached cursor that was transformed before storage is already broken.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_jYwjtSxj-s9iJ4vXmYAZBw
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.