{"record":{"id":"5ae29876b69ac2cc","repo":"nautechsystems/nautilus_trader","slug":"ax-fills-returned-an-empty-page-with-a-next-cursor","errorCode":null,"errorMessage":"AX fills returned an empty page with a next_cursor","messagePattern":"AX fills returned an empty page with a next_cursor","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/architect_ax/src/http/client.rs","lineNumber":2294,"sourceCode":"                );\n                fills.push(fill);\n            }\n\n            if let Some(total_count) = expected_total {\n                anyhow::ensure!(\n                    fills.len() as i64 <= total_count,\n                    \"AX fills pagination returned more unique rows ({}) than total_count {total_count}\",\n                    fills.len()\n                );\n            }\n\n            match response.next_cursor {\n                Some(next_cursor) => {\n                    anyhow::ensure!(\n                        !next_cursor.is_empty(),\n                        \"AX fills returned an empty next_cursor\"\n                    );\n                    anyhow::ensure!(\n                        page_len > 0,\n                        \"AX fills returned an empty page with a next_cursor\"\n                    );\n                    anyhow::ensure!(\n                        seen_cursors.insert(next_cursor.clone()),\n                        \"AX fills pagination repeated cursor {next_cursor:?}\"\n                    );\n                    params.cursor = Some(next_cursor);\n                }\n                None => break,\n            }\n        }\n\n        if let Some(total_count) = expected_total {\n            anyhow::ensure!(\n                fills.len() as i64 == total_count,\n                \"AX fills pagination returned {} unique rows, expected {total_count}\",\n                fills.len()","sourceCodeStart":2276,"sourceCodeEnd":2312,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/a4b06ed870971b5671d12754ea138a3ab99b1dec/crates/adapters/architect_ax/src/http/client.rs#L2276-L2312","documentation":"The client received a page with zero fills but a non-null next_cursor. A cursor that yields no rows while claiming more results invites an unbounded or degenerate cursor chain, so the guard requires every continuation page to carry at least one row. The normal terminal condition is next_cursor: None.","triggerScenarios":"The start/end window matches no fills but the server does not terminate the cursor chain; rows moved out of the window mid-traversal (corrected timestamps) leaving a page empty; server bug at the boundary page of a large result set.","commonSituations":"Querying an empty time slice (e.g. a quiet day) on an AX version that always emits a cursor; passing start >= end or a window entirely in the future; racing fill corrections during pagination.","solutions":["Validate the window before calling: start < end, both anchored within the 7-day lookback relative to end, and end not in the future beyond server time","Retry once — boundary races are transient","Reproduce with curl using the exact start/end/limit/sort params and inspect whether an empty page really carries a cursor","If deterministic, report to AX: empty pages must not claim continuation"],"exampleFix":"// before — inverted window yields empty pages\nclient.request_fill_reports(account_id, Some(end_ns), Some(start_ns)).await\n// after — start strictly before end, within the 7-day cap\nclient.request_fill_reports(account_id, Some(start_ns), Some(end_ns)).await","handlingStrategy":"validation","validationCode":"// Validate the fills window before calling the API\nfn fills_window_valid(start: UnixNanos, end: UnixNanos, now: UnixNanos) -> bool {\n    start < end && (end.as_i64() - start.as_i64()) <= 7 * 24 * 60 * 60 * 1_000_000_000 && end <= now\n}\n\nif !fills_window_valid(start, end, clock.timestamp_ns()) {\n    anyhow::bail!(\"refusing fills query with invalid window {start}..{end}\");\n}\nclient.request_fill_reports(account_id, Some(start), Some(end)).await?;","typeGuard":null,"tryCatchPattern":"match client.request_fill_reports(account_id, Some(start), Some(end)).await {\n    Ok(reports) => Ok(reports),\n    Err(e) if e.to_string().contains(\"empty page with a next_cursor\") => {\n        log::error!(\"AX paged an empty result set; check window and retry: {e}\");\n        tokio::time::sleep(Duration::from_secs(1)).await;\n        client.request_fill_reports(account_id, Some(start), Some(end)).await\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Always pass explicit start < end anchored in the past rather than None bounds","Avoid windows that are entirely in the future or where start >= end","Smoke-test one quiet historical day before scheduling recurring reconciliation windows"],"tags":["architect-ax","fills","pagination","cursor","query-params"],"backgroundTag":"invalid-pagination-cursor","analyzedSha":"a4b06ed870971b5671d12754ea138a3ab99b1dec","analyzedAt":"2026-08-16T22:54:50.089Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}