we-promise/sure · error · IbkrItem::ReportParser::ParseError

Invalid IBKR Flex XML: #{e.message}

Error message

Invalid IBKR Flex XML: #{e.message}

What it means

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.

Source

Thrown at app/models/ibkr_item/report_parser.rb:22

  class ParseError < StandardError; end

  POSITION_VALUE_CONTAINER_NAMES = %w[ChangeInPositionValues].freeze
  POSITION_VALUE_ROW_NAMES = %w[ChangeInPositionValue].freeze
  CASH_REPORT_CONTAINER_NAMES = %w[CashReport CashReports].freeze
  CASH_REPORT_ROW_NAMES = %w[CashReport CashReportCurrency CashReportRow].freeze
  EQUITY_SUMMARY_CONTAINER_NAMES = %w[EquitySummaryInBase].freeze
  EQUITY_SUMMARY_ROW_NAMES = %w[EquitySummaryByReportDateInBase].freeze
  OPEN_POSITION_CONTAINER_NAMES = %w[OpenPositions].freeze
  OPEN_POSITION_ROW_NAMES = %w[OpenPosition].freeze
  TRADES_CONTAINER_NAMES = %w[Trades].freeze
  TRADE_ROW_NAMES = %w[Trade].freeze
  CASH_TRANSACTION_CONTAINER_NAMES = %w[CashTransactions].freeze
  CASH_TRANSACTION_ROW_NAMES = %w[CashTransaction].freeze

  def initialize(xml_body)
    @document = Nokogiri::XML(xml_body.to_s) { |config| config.strict.noblanks }
  rescue Nokogiri::XML::SyntaxError => e
    raise ParseError, "Invalid IBKR Flex XML: #{e.message}"
  end

  def parse
    validate_document!

    {
      metadata: root_metadata,
      accounts: flex_statements.map { |statement| parse_statement(statement) }
    }
  end

  private

    def validate_document!
      raise ParseError, "Invalid IBKR Flex XML: missing FlexQueryResponse root." unless @document.at_xpath("//FlexQueryResponse")
      raise ParseError, "Invalid IBKR Flex XML: no FlexStatement nodes found." if flex_statements.empty?
    end

View on GitHub (pinned to e69894adb9)

Solutions

  1. Capture the raw body passed to the parser and check it standalone (xmllint) to locate the syntax error position
  2. If the body is an HTML/text error page, fix the upstream request (token, query id) so IBKR returns the actual Flex report
  3. Rescue IbkrItem::ReportParser::ParseError around the import to fail the sync with a user-safe message and log the body for triage

Example fix

begin
  data = IbkrItem::ReportParser.new(body).parse
rescue IbkrItem::ReportParser::ParseError => e
  DebugLogEntry.capture(category: 'ibkr', level: 'error', message: e.message, source: 'IbkrItem::ReportParser')
  raise
end
Defensive patterns

Strategy: try-catch

Try / catch

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

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/68209c02c2357678. Report an issue: GitHub.