{"record":{"id":"467080a373a7c537","repo":"we-promise/sure","slug":"ibkr-flex-statement-is-still-being-generated","errorCode":null,"errorMessage":"IBKR Flex statement is still being generated.","messagePattern":"IBKR Flex statement is still being generated\\.","errorType":"exception","errorClass":"ApiError","httpStatus":null,"severity":"warning","filePath":"app/models/provider/ibkr_flex.rb","lineNumber":86,"sourceCode":"\n      reference_code\n    end\n\n    def poll_statement(reference_code)\n      attempts = 0\n\n      loop do\n        attempts += 1\n        response = with_retries(\"GetStatement\") do\n          self.class.get(\"/GetStatement\", query: { t: token, q: reference_code, v: 3 })\n        end\n\n        xml = parse_xml(response.body)\n        return response.body if xml.at_xpath(\"//FlexQueryResponse\")\n\n        error = response_error(xml, response)\n        if error.is_a?(ApiError) && PENDING_ERROR_CODES.include?(error.error_code.to_s)\n          raise ApiError.new(\"IBKR Flex statement is still being generated.\", error_code: error.error_code) if attempts >= MAX_POLL_ATTEMPTS\n\n          sleep(POLL_INTERVAL)\n          next\n        end\n\n        raise(error || ApiError.new(\"IBKR Flex returned an unexpected response.\", status_code: response.code, response_body: response.body))\n      end\n    end\n\n    def response_error(xml, response)\n      error_code = xml.at_xpath(\"//ErrorCode\")&.text.to_s.strip.presence\n      error_message = xml.at_xpath(\"//ErrorMessage\")&.text.to_s.strip.presence\n\n      return nil if error_code.blank? && response.success?\n\n      message = error_message.presence || \"IBKR Flex request failed\"\n\n      case error_code","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/ibkr_flex.rb#L68-L104","documentation":"IBKR generates Flex statements asynchronously: SendRequest queues the report, GetStatement returns ErrorCode 1004/1019 while it is still being produced. poll_statement sleeps POLL_INTERVAL=3s and retries up to MAX_POLL_ATTEMPTS=20 (~60s total); if the statement still is not ready, this ApiError (with the pending error_code attached) is raised.","triggerScenarios":"A Flex Query covering years of data and many sections (trades, transfers, positions...) on a large account; IBKR backend slowness at month-end or during statement cycles; first-ever run of a newly created query that has to generate a long backfill.","commonSituations":"Accounts with heavy trading history, date ranges spanning years, queries including multiple report sections, IBKR data-center congestion - generation simply takes longer than the built-in ~60s budget.","solutions":["Retry download_statement later (schedule a second pass in a few minutes) - generation usually completes on its own","Shrink the Flex Query: shorter date range, fewer sections, split into multiple queries","If statements routinely exceed 60s for your accounts, raise MAX_POLL_ATTEMPTS or POLL_INTERVAL - but note the sleep blocks the worker thread, so prefer a background job with a longer budget","Keep error.error_code (1004/1019) attached when surfacing, so callers can distinguish 'still pending' from hard failures"],"exampleFix":"# caller - the statement usually finishes if you come back later\n# before\nstatement = flex.download_statement\n\n# after\nbegin\n  statement = flex.download_statement\nrescue Provider::IbkrFlex::ApiError => e\n  raise unless %w[1004 1019].include?(e.error_code.to_s)\n  FlexDownloadJob.perform_later(account, wait: 5.minutes)\n  raise\nend","handlingStrategy":"retry","validationCode":null,"typeGuard":"def ibkr_statement_pending?(error)\n  error.is_a?(Provider::IbkrFlex::ApiError) &&\n    %w[1004 1019].include?(error.error_code.to_s)\nend","tryCatchPattern":"begin\n  statement = flex.download_statement\nrescue Provider::IbkrFlex::ApiError => e\n  raise unless %w[1004 1019].include?(e.error_code.to_s)\n  raise if attempts >= 2 # give up after 2 reschedules\n  FlexDownloadJob.perform_later(account, wait: 5.minutes)\nend","preventionTips":["Keep Flex Queries small: short date ranges and only needed sections","Run downloads in a background job - the internal polling already sleeps up to ~60s","Schedule around IBKR statement-cycle peaks (month-end)","Distinguish pending (1004/1019) from hard errors before alerting"],"tags":["ibkr-flex","polling","async-statement","getstatement","backoff"],"backgroundTag":"async-job-polling-timeout","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}