{"record":{"id":"e64b635c0e16326f","repo":"nautechsystems/nautilus_trader","slug":"ax-fills-returned-an-empty-next-cursor","errorCode":null,"errorMessage":"AX fills returned an empty next_cursor","messagePattern":"AX fills returned an empty next_cursor","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/architect_ax/src/http/client.rs","lineNumber":2290,"sourceCode":"                anyhow::ensure!(\n                    seen_trade_ids.insert(fill.trade_id.clone()),\n                    \"AX fills pagination returned duplicate trade ID {}\",\n                    fill.trade_id\n                );\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 {","sourceCodeStart":2272,"sourceCodeEnd":2308,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/a4b06ed870971b5671d12754ea138a3ab99b1dec/crates/adapters/architect_ax/src/http/client.rs#L2272-L2308","documentation":"The /fills response carried a next_cursor that is present but an empty string. The client cannot distinguish an empty cursor from a real continuation token, and treating it as either end-of-chain or a fetch key risks skipping data or looping, so it fails fast. A legal end of the chain is next_cursor: null/absent.","triggerScenarios":"AX serializes the end-of-chain or unknown cursor as \"\" instead of null in some edge case (empty result set, filter matching nothing, boundary page); an API version change to cursor serialization; a hand-rolled mock returning empty strings.","commonSituations":"Querying a window with zero fills where the server still emits a cursor field; upgrading the AX API or the adapter in mismatched pairs; test harnesses with default-initialized string fields.","solutions":["Reproduce with curl: call GET /fills with the failing cursor and inspect the raw next_cursor field","Check the AX API changelog for cursor serialization changes between your client and server versions","If AX legitimately emits \"\" as end-of-chain, fix the response deserialization upstream (map empty string to None) via a PR — keep the ensure! intact","Retry once to rule out transient proxy corruption"],"exampleFix":"// before (stub server)\n{\"fills\": [], \"next_cursor\": \"\"}\n// after\n{\"fills\": [], \"next_cursor\": null}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match client.request_fill_reports(account_id, start, end).await {\n    Ok(reports) => Ok(reports),\n    Err(e) if e.to_string().contains(\"empty next_cursor\") => {\n        log::error!(\"AX sent an empty-string cursor; possible API serialization change: {e}\");\n        Err(e) // do not retry: deterministic server serialization behavior\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Contract-test the /fills response shape (cursor null vs empty) in CI against your AX environment","Run a smoke reconciliation after any AX API or adapter upgrade before enabling live tasks","Keep raw response logging available in staging to confirm cursor serialization changes quickly"],"tags":["architect-ax","fills","pagination","cursor","rest-api"],"backgroundTag":"invalid-pagination-cursor","analyzedSha":"a4b06ed870971b5671d12754ea138a3ab99b1dec","analyzedAt":"2026-08-16T22:54:50.089Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}