{"record":{"id":"ff1b36cf092f1d43","repo":"nautechsystems/nautilus_trader","slug":"ax-fills-total-count-changed-during-pagination-ex","errorCode":null,"errorMessage":"AX fills total_count changed during pagination: expected {expected}, was {total_count}","messagePattern":"AX fills total_count changed during pagination: expected (.+?), was (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/architect_ax/src/http/client.rs","lineNumber":2262,"sourceCode":"            if let Some(limit) = response.limit {\n                anyhow::ensure!(\n                    (0..=PAGE_SIZE).contains(&limit),\n                    \"AX fills applied limit must be between 0 and {PAGE_SIZE}, was {limit}\"\n                );\n                anyhow::ensure!(\n                    page_len <= limit as usize,\n                    \"AX fills page length {page_len} exceeds applied limit {limit}\"\n                );\n            }\n\n            if let Some(total_count) = response.total_count {\n                anyhow::ensure!(\n                    total_count >= 0,\n                    \"AX fills total_count must be non-negative, was {total_count}\"\n                );\n\n                if let Some(expected) = expected_total {\n                    anyhow::ensure!(\n                        total_count == expected,\n                        \"AX fills total_count changed during pagination: expected {expected}, was {total_count}\"\n                    );\n                } else {\n                    expected_total = Some(total_count);\n                }\n            }\n\n            for fill in response.fills {\n                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 {","sourceCodeStart":2244,"sourceCodeEnd":2280,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/a4b06ed870971b5671d12754ea138a3ab99b1dec/crates/adapters/architect_ax/src/http/client.rs#L2244-L2280","documentation":"The client pins total_count from the first /fills page and requires every subsequent page in the cursor chain to report the identical value. A changed total_count means the pages no longer form a consistent set (rows were added, removed, or restated mid-traversal), so the traversal aborts. Fills are fetched newest-first, so live executions during pagination are the classic cause.","triggerScenarios":"request_fill_reports runs while the account is actively trading: a new fill lands between fetching page N and page N+1 and AX reports an incremented total_count. Also triggered when AX corrects or restates historical rows during the traversal.","commonSituations":"Running fills reconciliation (e.g. report/generation tasks) concurrently with live strategy execution; two strategy instances pulling fills for the same account; passing end=None so the window extends to call time while fills keep arriving.","solutions":["Retry the full request_fill_reports call — it usually converges once trading pauses","Anchor the query with an explicit start and end (both within the 7-day AX cap) taken before you begin, and prefer a window that is already historical","Narrow the time window so the cursor chain is shorter and less exposed to concurrent inserts","If it reproduces on a static, closed window, capture the raw page sequence with curl and report to AX — the total is being mutated server-side"],"exampleFix":"// before — open-ended window, races with live fills\nlet reports = client.request_fill_reports(account_id, None, None).await?;\n// after — fixed historical window + retry\nlet end = clock.timestamp_ns();\nlet start = end - 6 * 24 * 60 * 60 * 1_000_000_000;\nlet reports = retry_on_pagination_error(3, || {\n    client.request_fill_reports(account_id, Some(start), Some(end))\n}).await?;","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for attempt in 0..3 {\n    match client.request_fill_reports(account_id, Some(start), Some(end)).await {\n        Ok(reports) => return Ok(reports),\n        Err(e) if e.to_string().contains(\"total_count changed during pagination\") => {\n            log::warn!(\"fills total drifted (attempt {}); backing off\", attempt + 1);\n            tokio::time::sleep(Duration::from_secs(2u64.pow(attempt))).await;\n        }\n        Err(e) => return Err(e.into()),\n    }\n}\nbail!(\"fills pagination unstable after retries\");","preventionTips":["Anchor start/end to fixed timestamps before the call instead of None windows","Schedule fills reconciliation during quiet trading periods or pause execution during the pull","Keep windows small (day slices) so traversals finish before totals can drift"],"tags":["architect-ax","fills","pagination","consistency","race-condition"],"backgroundTag":"pagination-invariant-violation","analyzedSha":"a4b06ed870971b5671d12754ea138a3ab99b1dec","analyzedAt":"2026-08-16T22:54:50.089Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}