{"record":{"id":"5d27e959c4c0f517","repo":"nautechsystems/nautilus_trader","slug":"run-config-id-is-required-when-a-backtestnode-is-p","errorCode":null,"errorMessage":"run_config_id is required when a BacktestNode is provided","messagePattern":"run_config_id is required when a BacktestNode is provided","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/nautilus_trader/analysis/tearsheet.py","lineNumber":502,"sourceCode":"\n\ndef _create_tearsheet_from_result(\n    result: BacktestResult,\n    node: BacktestNode | None,\n    run_config_id: str | None,\n    currency,\n    output_path: str | None,\n    title: str,\n    config,\n    benchmark_returns: pd.Series | None,\n    benchmark_name: str,\n) -> str | None:\n    resolved_run_config_id = run_config_id or result.run_config_id\n    needs_engine = config is not None and \"bars_with_fills\" in config.chart_names\n    if needs_engine and node is None:\n        raise ValueError(\"A BacktestNode is required for the bars_with_fills chart\")\n    if node is not None and resolved_run_config_id is None:\n        raise ValueError(\"run_config_id is required when a BacktestNode is provided\")\n    if node is not None and resolved_run_config_id is not None:\n        _validate_result_node_state(node, resolved_run_config_id)\n\n    engine_view = (\n        _BacktestNodeEngineView(node, resolved_run_config_id)\n        if node is not None and resolved_run_config_id is not None\n        else None\n    )\n    returns = _result_returns_series(result)\n    run_info = _result_run_info(result)\n    account_info = _result_account_info(result, node, resolved_run_config_id, currency)\n\n    if title == \"NautilusTrader Backtest Results\":\n        run_started = _format_optional_iso8601(result.run_started)\n        title = f\"<b>NautilusTrader</b> v{NAUTILUS_VERSION} - Backtest Results\"\n        title += f\"<br><sub>Run started: {run_started}</sub>\"\n\n    return create_tearsheet_from_stats(","sourceCodeStart":484,"sourceCodeEnd":520,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/a4b06ed870971b5671d12754ea138a3ab99b1dec/python/nautilus_trader/analysis/tearsheet.py#L484-L520","documentation":"After a successful HTTP login, BetfairHttpClient::session_token() returned None while the data client was setting up its market stream. session_token() reads a shared slot that connect() fills on login and that disconnect()/reconnect() clear, so a None here means the session was cleared between login and read (a concurrent disconnect or reconnect race), not a Betfair-side rejection.","triggerScenarios":"Another task calls disconnect() or reconnect() on the same BetfairHttpClient while DataClient::connect() is between login and stream setup; stopping the node mid-connect; sharing one HTTP client across components that race on session state.","commonSituations":"Shutdown signal arriving during startup; two owners driving the same client (e.g. data and exec clients built on one HTTP client); supervision logic that disconnects on a timeout while connect is still in flight.","solutions":["Ensure a single task owns connect/disconnect for the client; never disconnect while connect is in flight","Retry connect() - a clean re-login repopulates the token slot","Check is_connected() before requesting a restart to avoid racing states"],"exampleFix":"// before\nlet token = self.http_client.session_token().await\n    .ok_or_else(|| anyhow::anyhow!(\"No session token after login\"))?;\n\n// after: re-login once if the slot was cleared by a race\nlet token = match self.http_client.session_token().await {\n    Some(t) => t,\n    None => {\n        self.http_client.reconnect().await?;\n        self.http_client.session_token().await\n            .ok_or_else(|| anyhow::anyhow!(\"No session token after login\"))?\n    }\n};","handlingStrategy":"validation","validationCode":"if !http_client.is_connected().await {\n    anyhow::bail!(\"Betfair HTTP session missing; connect before requesting the token\");\n}\nlet token = http_client.session_token().await\n    .ok_or_else(|| anyhow::anyhow!(\"session token cleared by a concurrent disconnect\"))?;","typeGuard":"async fn has_live_session(client: &BetfairHttpClient) -> bool {\n    client.is_connected().await\n}","tryCatchPattern":"match http_client.session_token().await {\n    Some(t) => { /* proceed with stream setup */ }\n    None => {\n        // session cleared mid-connect: re-login and retry once, else surface the race\n        http_client.reconnect().await?;\n    }\n}","preventionTips":["Own connect/disconnect from a single task per client","Never call disconnect() while connect() is in flight","Check is_connected() before reading the session token"],"tags":["betfair","session","race-condition","authentication","rust"],"backgroundTag":"session-token-missing","analyzedSha":"a4b06ed870971b5671d12754ea138a3ab99b1dec","analyzedAt":"2026-08-16T22:54:50.089Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}