nautechsystems/nautilus_trader · error · anyhow::Error
AX fills pagination repeated cursor {next_cursor:?}
Error message
AX fills pagination repeated cursor {next_cursor:?} What it means
Every accepted next_cursor is inserted into a seen_cursors HashSet; a cursor repeating within one traversal means the chain loops, and continuing would fetch the same pages forever. The guard converts that would-be infinite loop into an immediate error with the offending cursor value.
Source
Thrown at crates/adapters/architect_ax/src/http/client.rs:2298
if let Some(total_count) = expected_total {
anyhow::ensure!(
fills.len() as i64 <= total_count,
"AX fills pagination returned more unique rows ({}) than total_count {total_count}",
fills.len()
);
}
match response.next_cursor {
Some(next_cursor) => {
anyhow::ensure!(
!next_cursor.is_empty(),
"AX fills returned an empty next_cursor"
);
anyhow::ensure!(
page_len > 0,
"AX fills returned an empty page with a next_cursor"
);
anyhow::ensure!(
seen_cursors.insert(next_cursor.clone()),
"AX fills pagination repeated cursor {next_cursor:?}"
);
params.cursor = Some(next_cursor);
}
None => break,
}
}
if let Some(total_count) = expected_total {
anyhow::ensure!(
fills.len() as i64 == total_count,
"AX fills pagination returned {} unique rows, expected {total_count}",
fills.len()
);
}
let ts_init = self.generate_ts_init();View on GitHub (pinned to a4b06ed870)
Solutions
- Retry the full traversal — transient cursor-cache issues on the server often clear
- Capture the full cursor chain with curl and verify no legitimate loop exists in the raw responses
- Confirm the adapter version matches the AX API version you connect to (cursor formats change between versions)
- Report to AX with the chain if it loops deterministically — this cannot be worked around client-side
Defensive patterns
Strategy: try-catch
Try / catch
match client.request_fill_reports(account_id, start, end).await {
Ok(reports) => Ok(reports),
Err(e) if e.to_string().contains("repeated cursor") => {
log::error!("AX cursor chain loops on {e}; aborting to avoid infinite pagination");
Err(e) // never retry-loop here — the guard exists to stop a server-side loop
}
Err(e) => Err(e),
} Prevention
- Treat repeated-cursor errors as a server incident signal, not transient noise — alert ops
- Keep adapter and AX API versions in lockstep; cursor formats are version-sensitive
- Cap reconciliation frequency so a looping chain cannot hammer the API via scheduled retries
When it happens
Trigger: AX echoes the request cursor back when it has nothing new to serve; the cursor generation has a bug and re-issues a token for an already-served page; a page of exactly PAGE_SIZE rows triggers stale cursor regeneration at the boundary.
Common situations: Long-running reconciliation traversals that hit server cursor-cache expiry mid-chain; mismatched adapter/server API versions; paginating with parameters the server's cursor implementation does not support (custom sort).
Related errors
- AX fills returned an empty next_cursor
- AX fills returned an empty page with a next_cursor
- AX fills total_count must be non-negative, was {total_count}
- AX fills total_count changed during pagination: expected {ex
- AX fills pagination returned duplicate trade ID {}
AI-assisted analysis of nautechsystems/nautilus_trader@a4b06ed870 (2026-08-16).
Data as JSON: /api/errors/d4e2f6fe4dcc25b0.
Report an issue: GitHub.