Repository navigation
Description
Summary
SEQUENCED_CHANNELS includes order_group_updates, so gaps ARE detected on that channel — SequenceTracker.track() fires on_gap and returns False so dispatch is skipped. But _handle_seq_gap (in kalshi/ws/client.py) only has a recovery branch for sub.channel == 'orderbook_delta'. For an order_group_updates gap:
- The gap-causing message is dropped (correctly — dispatch was skipped).
- The seq tracker advances
_last_seq[sid]to the new seq (sequence.py:70). - No resubscribe, no state reset, no signal to the consumer.
The consumer iterator silently misses one or more events. For order-group machinery tracking contracts_limit / event_type transitions, this leaves the consumer's view permanently diverged from the server.
This is a sibling of the orderbook-resync issue but on a different channel and with no recovery code path at all (orderbook at least clears local state).
Location
kalshi/ws/client.py:393-403— branch only fororderbook_deltakalshi/ws/sequence.py:68-75— gap path advances watermark before returningFalse
Evidence
# kalshi/ws/client.py:393-403
if sub and sub.channel == "orderbook_delta":
tickers = sub.params.get("market_tickers", [])
...
# ← order_group_updates falls off the end; no recovery# kalshi/ws/sequence.py:68-75
gap = SequenceGap(sid=sid, expected=expected, received=seq)
logger.warning("Sequence gap: ...")
self._last_seq[sid] = seq # advance, so future gaps won't fire here either
if self._on_gap is not None:
await self._on_gap(gap)
return FalseRecommended fix
Route non-orderbook sequenced channels through a generic resubscribe helper. For order_group_updates:
- Unsubscribe and re-subscribe to the affected sid (the server will re-emit the current order-group state).
- Until the resubscribe completes, stash incoming frames (the resubscribe-window mechanism from WS: data frames arriving during reconnect+resubscribe are dropped instead of stashed #176 already exists in
SubscriptionManager— reuse it). - If resubscribe fails, surface
KalshiSequenceGapErrorthrough the consumer iterator (see separate "WS error context" issue) withchannel='order_group_updates',sid,last_seq,next_seq.
Add a regression test that injects a gap on order_group_updates (e.g., via direct SequenceTracker._last_seq[sid] = 5 then send seq=10) and asserts a recovery action fires.
This issue and the orderbook-resync issue (critical) share the same _handle_seq_gap body — fix them together so the per-channel routing is unified.
Severity & category
medium / correctness, ws
Activity
fix(ws): orderbook resync; seq reset detection; permanent close codes…