Skip to content

KeyError in Wrapper.contractDetails on Warning 1102 server-side re-emission of previously-qualified contracts #219

@Kai-Y-Chen

Description

@Kai-Y-Chen

Bug

After IBKR's Warning 1102: "Connectivity between IBKR and Trader Workstation has been restored - data maintained" fires (a brief
server-side disconnect/reconnect with session state preserved),
the server re-emits contractDetails messages for previously-
qualified contracts. Wrapper.contractDetails accesses
self._results[reqId] directly, which raises KeyError because
the entry was cleaned up by _endReq when the original
qualifyContracts future resolved.

The exception is caught by Decoder.interpret so it isn't fatal,
but produces noisy ERROR logs (one per re-emitted contract) that
look alarming and bury legitimate errors.

Observed behavior

Production deployment qualifying 4 contracts at startup
(IB.qualifyContractsAsync), then handling a Warning 1102
event a few hours later, produced the following sequence (4
KeyErrors immediately followed by the 1102 warning):

ERROR ib_async.Decoder Error handling fields: ['10', '17', 'ZN', 'FUT', '20260618 ...
Traceback (most recent call last):
  File "/app/deps/ib_async/decoder.py", line 181, in interpret
    handler(fields)
  File "/app/deps/ib_async/decoder.py", line 344, in contractDetails
    self.wrapper.contractDetails(int(reqId), cd)
  File "/app/deps/ib_async/wrapper.py", line 874, in contractDetails
    self._results[reqId].append(contractDetails)
KeyError: 17

ERROR ib_async.Decoder Error handling fields: ['10', '19', 'ES', 'FUT', '20260618 ...
... (KeyError: 19) ...

ERROR ib_async.Decoder Error handling fields: ['10', '13', 'ZB', 'FUT', '20260618 ...
... (KeyError: 13) ...

ERROR ib_async.Decoder Error handling fields: ['10', '15', 'SPY', 'STK', '' ...
... (KeyError: 15) ...

ERROR ib_async.wrapper Error 1102, reqId -1: Connectivity between IBKR and Trader
Workstation has been restored - data maintained. All data farms are connected:
usfuture; usfarm; ushmds; secdefnj.

Note: each KeyError's reqId (17/19/13/15) matches a reqId from
the prior IB.qualifyContractsAsync call ~6 hours earlier. The
futures had already been qualified, Future resolved, and
_results[reqId] popped by _endReq at that point.

Root cause

wrapper.py:873-874:

def contractDetails(self, reqId: int, contractDetails: ContractDetails):
    self._results[reqId].append(contractDetails)

_results[reqId] is created by _startReq and popped by
_endReq(reqId) (which fires on contractDetailsEnd). The
normal request/response cycle is fully covered, but the
1102-driven server-side re-emission arrives after the cycle is
already complete.

Suggested fix

Defensive lookup — drop silently (or at DEBUG level) when the
request has already been ended:

def contractDetails(self, reqId: int, contractDetails: ContractDetails):
    if reqId in self._results:
        self._results[reqId].append(contractDetails)

bondContractDetails = contractDetails at line 876 then inherits
the fix.

I'd be happy to send a PR.

Why not reproduce minimally

Warning 1102 is server-side (IBKR-initiated brief disconnect with
session state preserved); we can't trigger it deterministically
from a client-side test. The simplest synthetic repro would be to
unit-test Wrapper.contractDetails(int, ContractDetails) after
calling _endReq(reqId) directly — that would exercise the same
KeyError path without needing a real Gateway session.

Related

Issue #128 (KeyError: 'completedOrders') is a similar-shape
late-arrival pattern in a different handler; the underlying
"handler doesn't tolerate post-_endReq arrivals" issue likely
applies more broadly than just contractDetails.

Environment

  • ib_async 2.1.0 (PyPI)
  • Python 3.14.4 on Debian 12 (python:3.14-slim)
  • IB Gateway v10.45 (paper, native install, socat-localhost-
    injection on a dedicated VM)
  • Connection clientId=2, qualify pattern:
    IB.qualifyContractsAsync(Future(...)) × 4 at startup, then
    long-running event loop with reqMktData subscriptions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions