Hmbown/CodeWhale · error · anyhow::Error

{} repeated pagination cursor; aborting

Error message

{} repeated pagination cursor; aborting

What it means

After each catalogue page, observe_page records the returned nextCursor in a HashSet; if a cursor that was already seen appears again, pagination would loop forever and the method aborts (crates/tui/src/mcp.rs:1332-1340). This is an infinite-loop guard against servers whose cursor generation regresses or echoes the cursor that was just sent.

Source

Thrown at crates/tui/src/mcp.rs:1339

                self.method,
                MAX_MCP_CATALOG_ITEMS
            );
        }
        if self.bytes > MAX_MCP_CATALOG_BYTES {
            anyhow::bail!(
                "{} exceeded the {}-byte aggregate catalogue limit",
                self.method,
                MAX_MCP_CATALOG_BYTES
            );
        }
        let cursor = result
            .get("nextCursor")
            .and_then(|value| value.as_str())
            .map(str::to_owned);
        if let Some(cursor) = cursor.as_ref()
            && !self.seen_cursors.insert(cursor.clone())
        {
            anyhow::bail!("{} repeated pagination cursor; aborting", self.method);
        }
        Ok(cursor)
    }
}

fn is_legacy_sse_transport(config: &McpServerConfig) -> bool {
    config
        .transport
        .as_deref()
        .map(|transport| transport.trim().eq_ignore_ascii_case("sse"))
        .unwrap_or(false)
}

pub fn validate_mcp_transport(transport: Option<&str>) -> Result<()> {
    let Some(transport) = transport else {
        return Ok(());
    };
    if transport.trim().eq_ignore_ascii_case("sse") {

View on GitHub (pinned to 8880682c63)

Solutions

  1. Fix the server: cursors must advance and never repeat; opaque stateless tokens (e.g. encoding the offset) are the robust pattern.
  2. If the catalogue is small, disable server-side pagination and return everything on one page with no nextCursor.
  3. Ensure load-balanced instances share cursor state or use sticky sessions.
  4. Retry after a server restart has settled - transient resets clear themselves.

Example fix

# before (server bug): every page returns nextCursor="page-1"
#   tools/list repeated pagination cursor; aborting
# after (server): cursor derived from a monotonic offset, never repeated
#   cursor = base64(f"{offset}:{catalogue_version}")
Defensive patterns

Strategy: validation

Validate before calling

#!/usr/bin/env bash
# Walk the catalogue once and fail if any cursor repeats.
url="https://mcp.example.com/mcp"; cursor=""; seen=""
while :; do
  body=$(curl -s "$url" -H 'content-type: application/json' \
    -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"${cursor:+,\"params\":{\"cursor\":\"$cursor\"}}}")
  cursor=$(printf '%s' "$body" | jq -r '.result.nextCursor // empty')
  [ -z "$cursor" ] && break
  case " $seen " in *" $cursor "*) echo "cursor repeated: $cursor"; exit 1;; esac
  seen="$seen $cursor"
done; echo "cursors unique"

Prevention

When it happens

Trigger: A server that ignores the cursor parameter and always returns the same first-page cursor; server state resetting mid-pagination (restart, cache eviction); a load balancer alternating between instances with independent cursor state.

Common situations: Stateless server implementations that do not really implement cursors; rolling restarts during discovery; sticky-session-less LBs in front of stateful MCP servers.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/42aaa582abeb2f1a. Report an issue: GitHub.