{"record":{"id":"68209c02c2357678","repo":"we-promise/sure","slug":"invalid-ibkr-flex-xml-e-message","errorCode":null,"errorMessage":"Invalid IBKR Flex XML: #{e.message}","messagePattern":"Invalid IBKR Flex XML: #(.+?)","errorType":"exception","errorClass":"IbkrItem::ReportParser::ParseError","httpStatus":null,"severity":"error","filePath":"app/models/ibkr_item/report_parser.rb","lineNumber":22,"sourceCode":"  class ParseError < StandardError; end\n\n  POSITION_VALUE_CONTAINER_NAMES = %w[ChangeInPositionValues].freeze\n  POSITION_VALUE_ROW_NAMES = %w[ChangeInPositionValue].freeze\n  CASH_REPORT_CONTAINER_NAMES = %w[CashReport CashReports].freeze\n  CASH_REPORT_ROW_NAMES = %w[CashReport CashReportCurrency CashReportRow].freeze\n  EQUITY_SUMMARY_CONTAINER_NAMES = %w[EquitySummaryInBase].freeze\n  EQUITY_SUMMARY_ROW_NAMES = %w[EquitySummaryByReportDateInBase].freeze\n  OPEN_POSITION_CONTAINER_NAMES = %w[OpenPositions].freeze\n  OPEN_POSITION_ROW_NAMES = %w[OpenPosition].freeze\n  TRADES_CONTAINER_NAMES = %w[Trades].freeze\n  TRADE_ROW_NAMES = %w[Trade].freeze\n  CASH_TRANSACTION_CONTAINER_NAMES = %w[CashTransactions].freeze\n  CASH_TRANSACTION_ROW_NAMES = %w[CashTransaction].freeze\n\n  def initialize(xml_body)\n    @document = Nokogiri::XML(xml_body.to_s) { |config| config.strict.noblanks }\n  rescue Nokogiri::XML::SyntaxError => e\n    raise ParseError, \"Invalid IBKR Flex XML: #{e.message}\"\n  end\n\n  def parse\n    validate_document!\n\n    {\n      metadata: root_metadata,\n      accounts: flex_statements.map { |statement| parse_statement(statement) }\n    }\n  end\n\n  private\n\n    def validate_document!\n      raise ParseError, \"Invalid IBKR Flex XML: missing FlexQueryResponse root.\" unless @document.at_xpath(\"//FlexQueryResponse\")\n      raise ParseError, \"Invalid IBKR Flex XML: no FlexStatement nodes found.\" if flex_statements.empty?\n    end\n","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/ibkr_item/report_parser.rb#L4-L40","documentation":"IbkrItem::ReportParser parses the Flex Query response with Nokogiri in strict mode (config.strict.noblanks); any XML::SyntaxError is converted to ParseError with the underlying parser message. The parser expects well-formed IBKR Flex XML — HTML pages or plain-text error bodies fail here.","triggerScenarios":"Feeding ReportParser.new(xml_body) a malformed document: a truncated download, an HTML error page saved as the report, encoding corruption, or a test stub returning non-XML.","commonSituations":"IBKR returning an error page instead of the report for bad tokens; proxies or middlewares truncating responses; hand-edited fixtures that are no longer valid XML.","solutions":["Capture the raw body passed to the parser and check it standalone (xmllint) to locate the syntax error position","If the body is an HTML/text error page, fix the upstream request (token, query id) so IBKR returns the actual Flex report","Rescue IbkrItem::ReportParser::ParseError around the import to fail the sync with a user-safe message and log the body for triage"],"exampleFix":"begin\n  data = IbkrItem::ReportParser.new(body).parse\nrescue IbkrItem::ReportParser::ParseError => e\n  DebugLogEntry.capture(category: 'ibkr', level: 'error', message: e.message, source: 'IbkrItem::ReportParser')\n  raise\nend","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"rescue IbkrItem::ReportParser::ParseError => e around ReportParser.new(body).parse; capture the raw body length/first bytes in a DebugLogEntry, then fail the sync with a user-safe message","preventionTips":["Fetch Flex reports with explicit charset handling and response-body logging at debug level","Never feed unverified HTML/error pages to the strict parser","Record the token/query id used per fetch so malformed responses can be correlated with credentials"],"tags":["ibkr","xml","nokogiri","parsing"],"backgroundTag":"xml-parse-error","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}