Hmbown/CodeWhale · error

repeated pagination cursor; aborting

Error message

{} repeated pagination cursor; aborting

What it means

During paginated catalogue discovery the client records every nextCursor the server returns in a seen_cursors set. If the server returns a cursor that was already seen, pagination would loop forever, so the client bails out. This guards against broken MCP servers that keep replaying the same page.

Solutions

  1. Fix or upgrade the MCP server so it advances pagination correctly
  2. Restart the MCP server — a stale in-memory pager can replay cursors
  3. Bypass pagination by reducing the catalogue so it fits in one page
  4. Check for caching middlewares between the client and server and disable them
Defensive patterns

Strategy: retry

Validate before calling

// probe pagination before long discovery: fetch one page and check the cursor differs from the request cursor
let probe = list_tools(None).await?;
if probe.next_cursor.as_deref() == Some("") && probe.tools.len() == page_limit { /* suspect broken pager */ }

Try / catch

match result {
    Err(e) if e.to_string().contains("repeated pagination cursor") => {
        // fall back to single-page listing or restart server
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling tools/list (or resource list) pagination against an MCP server whose nextCursor values repeat — the server ignores the cursor argument and returns page 1 forever, or returns a fixed cursor.

Common situations: Buggy or non-conformant MCP server implementations; proxies/caches replaying list responses; server-side pagination bugs after an upgrade.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/42aaa582abeb2f1a. Report an issue: GitHub.

Appendix: source

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

                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 73e0f67d83)