nautechsystems/nautilus_trader · error · anyhow::Error

AX fills returned an empty next_cursor

Error message

AX fills returned an empty next_cursor

What it means

If the server supplies a next_cursor, it must be a non-empty string — an empty cursor string cannot be sent back to request the next page and indicates a malformed paging response. The client rejects it so the caller doesn't loop or fail later with a bad request.

Solutions

  1. Capture the raw response JSON and confirm the empty next_cursor, then retry the pagination — often transient.
  2. Treat the current page as final if the server keeps doing this: check whether the client and server versions agree on cursor semantics.
  3. Report the empty-cursor response to the AX service team with the request parameters used.
  4. If it coincides with an exact-multiple row count (page_len == limit), verify the server's end-of-data detection.
Defensive patterns

Strategy: try-catch

Try / catch

match client.paginate_fills(params).await {
    Ok(fills) => apply(fills),
    Err(e) if e.to_string().contains("empty next_cursor") => {
        warn!("AX blank cursor; treating current page as final if retry fails");
        client.paginate_fills(params).await.or_else(|_| fallback_partial_fetch(params))
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A fills page response where next_cursor is Some("") — server signaled 'more pages' but produced a blank cursor value.

Common situations: Server bug when the last page is exactly one row short; middleware trimming string fields; newer server serialization change the client wasn't built for.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/e64b635c0e16326f. Report an issue: GitHub.

Appendix: source

Thrown at crates/adapters/architect_ax/src/http/client.rs:2276

                anyhow::ensure!(
                    seen_trade_ids.insert(fill.trade_id.clone()),
                    "AX fills pagination returned duplicate trade ID {}",
                    fill.trade_id
                );
                fills.push(fill);
            }

            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 {

View on GitHub (pinned to 18893faf8b)