Fix string errors in `.ib.broker` relay
Normalize structured and raw IB errors without indexing strings as message dictionaries. Deats, - match each error form before request-id handling; - parse `[code N]` and `[Errno N]` prefixes without crashing; - cover raw errors through `deliver_trade_events()`. Prompt-IO: ai/prompt-io/opencode/20260828T063126Z_1606122b_prompt_io.md (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))wkt/fsp_backfill_sync
parent
1606122b45
commit
9115d24da0
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
model: openai/gpt-5.6-sol
|
||||
service: opencode
|
||||
session: pid-469264
|
||||
timestamp: 2026-08-28T06:31:26Z
|
||||
git_ref: 1606122b
|
||||
scope: code
|
||||
substantive: true
|
||||
raw_file: 20260828T063126Z_1606122b_prompt_io.raw.md
|
||||
---
|
||||
|
||||
## Prompt
|
||||
|
||||
Review the staged IB broker patch before committing it. After the review
|
||||
identified a deterministic error-parser crash, continue with the available
|
||||
next steps while leaving the typed EMS worktree and Ruff change untouched.
|
||||
|
||||
## Response summary
|
||||
|
||||
Repaired bracketed IB API error parsing by splitting on the matched error
|
||||
prefix. Added a deterministic regression that routes bracketed and plain
|
||||
strings through the real trade-event relay and verifies their EMS error
|
||||
messages.
|
||||
|
||||
## Files changed
|
||||
|
||||
- `piker/brokers/ib/broker.py` - safely extracts bracketed API error codes.
|
||||
- `tests/test_ib_broker.py` - covers bracketed and plain error relay inputs.
|
||||
|
||||
## Human edits
|
||||
|
||||
The human required review before the root commit, accepted the identified
|
||||
parser defect as the next work item, and explicitly authorized continuation.
|
||||
The pre-existing staged IB refactor is human-authored; the agent applied only
|
||||
the parser repair and focused regression described above.
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
model: openai/gpt-5.6-sol
|
||||
service: opencode
|
||||
timestamp: 2026-08-28T06:31:26Z
|
||||
git_ref: fix_symcache_search
|
||||
diff_cmd: git diff HEAD~1..HEAD
|
||||
---
|
||||
|
||||
## Prompt
|
||||
|
||||
The human asked for a review of the root checkout's staged IB patch before
|
||||
committing it. The review found that bracketed API error strings call
|
||||
`str.partition()` without its required separator. The human then instructed
|
||||
the agent to continue with available next steps, or ask only if unsure.
|
||||
|
||||
## Response
|
||||
|
||||
> `git diff HEAD~1..HEAD -- piker/brokers/ib/broker.py`
|
||||
|
||||
The generated parser repair uses the recognized `[code ` or `[Errno ` prefix
|
||||
to extract the numeric code without changing the surrounding event relay.
|
||||
|
||||
> `git diff HEAD~1..HEAD -- tests/test_ib_broker.py`
|
||||
|
||||
The generated regression drives bracketed and plain strings through
|
||||
`deliver_trade_events()` and verifies that one `BrokerdError` reaches EMS
|
||||
with the expected reason and unknown request ID.
|
||||
|
||||
The typed EMS worktree and the unrelated Ruff policy change were left
|
||||
untouched. No commit was created.
|
||||
|
|
@ -52,7 +52,6 @@ from ib_async.objects import (
|
|||
from piker import config
|
||||
from piker.log import (
|
||||
get_logger,
|
||||
get_console_log,
|
||||
)
|
||||
from piker.types import Struct
|
||||
from piker.accounting import (
|
||||
|
|
@ -1429,27 +1428,35 @@ async def deliver_trade_events(
|
|||
# NOTE: see impl deats in
|
||||
# `Client.inline_errors()::push_err()`
|
||||
err: dict|str = item
|
||||
is_msg: bool = False
|
||||
|
||||
# std case, never relay errors for non-order-control
|
||||
# related issues.
|
||||
# https://interactivebrokers.github.io/tws-api/message_codes.html
|
||||
if isinstance(err, dict):
|
||||
match err:
|
||||
case dict():
|
||||
# if isinstance(err, dict):
|
||||
code: int = err['error_code']
|
||||
reason: str = err['reason']
|
||||
reqid: str = str(err['reqid'])
|
||||
is_msg = True
|
||||
|
||||
case str():
|
||||
# XXX, sometimes you'll get just a `str` of the form,
|
||||
# '[code 104] connection failed' or something..
|
||||
elif isinstance(err, str):
|
||||
# elif isinstance(err, str):
|
||||
code: int|str = '<NA-err-code>'
|
||||
code_part, _, reason = err.rpartition(']')
|
||||
if code_part:
|
||||
for prefix_patt in [
|
||||
'[Errno ',
|
||||
'[code ',
|
||||
]:
|
||||
code_part, _, code = code_part.partition()
|
||||
if code:
|
||||
code = int(code)
|
||||
_, prefix, code_str = (
|
||||
code_part.partition(prefix_patt)
|
||||
)
|
||||
if prefix:
|
||||
code = int(code_str)
|
||||
break
|
||||
|
||||
reqid: str = '<unknown>'
|
||||
|
|
@ -1497,16 +1504,17 @@ async def deliver_trade_events(
|
|||
)
|
||||
continue
|
||||
|
||||
if err['reqid'] == -1:
|
||||
if (
|
||||
is_msg
|
||||
and
|
||||
err['reqid'] == -1
|
||||
):
|
||||
log.error(
|
||||
f'TWS external order error ??\n'
|
||||
f'{pformat(err)}\n'
|
||||
)
|
||||
|
||||
flow: dict = dict(
|
||||
flows.get(reqid)
|
||||
or {}
|
||||
)
|
||||
flow: dict = dict(flows.get(reqid) or {})
|
||||
|
||||
# TODO: we don't want to relay data feed / lookup errors
|
||||
# so we need some further filtering logic here..
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
'''
|
||||
IB broker event-relay regressions.
|
||||
|
||||
'''
|
||||
from collections.abc import AsyncIterator
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
import trio
|
||||
|
||||
from piker.brokers.ib.broker import deliver_trade_events
|
||||
from piker.clearing import OrderDialogs
|
||||
from piker.clearing._messages import BrokerdError
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('error', 'expected_reason'),
|
||||
[
|
||||
(
|
||||
'[code 104] connection failed',
|
||||
' connection failed',
|
||||
),
|
||||
(
|
||||
'[Errno 111] Connect call failed',
|
||||
' Connect call failed',
|
||||
),
|
||||
(
|
||||
'plain API error',
|
||||
'plain API error',
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_trade_error_strings_are_relayed(
|
||||
error: str,
|
||||
expected_reason: str,
|
||||
) -> None:
|
||||
'''
|
||||
Raw IB API error strings must not kill the trade-event relay.
|
||||
|
||||
``Client.inline_errors()`` can emit bracketed ``[code N]`` and
|
||||
``[Errno N]`` strings instead of structured error dictionaries.
|
||||
The string branch called ``str.partition()`` without a separator,
|
||||
raising ``TypeError`` before EMS received the error and
|
||||
terminating all later trade-event delivery. Feed both documented
|
||||
bracket forms and an unprefixed control through
|
||||
``deliver_trade_events()``. The exact emitted reason and unknown
|
||||
request ID prove each event crossed the real relay path without
|
||||
crashing or being misclassified.
|
||||
|
||||
'''
|
||||
class FakeEmsStream:
|
||||
'''
|
||||
Collect messages sent to the EMS stream.
|
||||
|
||||
'''
|
||||
def __init__(self) -> None:
|
||||
'''
|
||||
Initialize the captured message sequence.
|
||||
|
||||
'''
|
||||
self.messages: list[Any] = []
|
||||
|
||||
async def send(self, msg: Any) -> None:
|
||||
'''
|
||||
Capture one relayed broker message.
|
||||
|
||||
'''
|
||||
self.messages.append(msg)
|
||||
|
||||
async def events() -> AsyncIterator[tuple[str, str]]:
|
||||
'''
|
||||
Emit the raw IB error under test.
|
||||
|
||||
'''
|
||||
yield 'error', error
|
||||
|
||||
async def main() -> list[Any]:
|
||||
'''
|
||||
Drive the broker relay to input exhaustion.
|
||||
|
||||
'''
|
||||
ems_stream = FakeEmsStream()
|
||||
await deliver_trade_events(
|
||||
events(),
|
||||
ems_stream,
|
||||
accounts_def={},
|
||||
proxies={},
|
||||
ledgers={},
|
||||
tables={},
|
||||
flows=OrderDialogs(_flows={}),
|
||||
)
|
||||
return ems_stream.messages
|
||||
|
||||
messages = trio.run(main)
|
||||
assert len(messages) == 1
|
||||
msg = messages[0]
|
||||
assert isinstance(msg, BrokerdError)
|
||||
assert msg.reqid == '<unknown>'
|
||||
assert msg.reason == expected_reason
|
||||
Loading…
Reference in New Issue