Skip to content

Errors: KalshiSequenceGapError + KalshiSubscriptionError lack channel / sid / seq context #213

Description

@TexasCoding

Summary

KalshiSequenceGapError and KalshiSubscriptionError lack the programmatic context a caller needs to write a recovery handler.

KalshiSequenceGapError

Bare subclass with only a docstring — no channel, sid, last_seq, next_seq attributes. Per docs/websockets.md:196-198 and docs/errors.md:124-127 the class exists "for callers wiring their own resync handlers" on top of subscribe(channel, ...). A caller catching it gets only str(e) — they can't tell which channel/sid is gapped or how far behind they are without parsing the message string.

Compare to KalshiRateLimitError (exposes .retry_after) — the asymmetry is the gap.

KalshiSubscriptionError

Exposes only error_code and the message string. A caller catching it cannot programmatically tell which channel or subscription failed, which matters when several subscribe_* calls land in parallel.

Location

kalshi/errors.py:76-89

Evidence

# kalshi/errors.py:76-77
class KalshiSequenceGapError(KalshiWebSocketError):
    """Sequence gap detected that could not be resolved via resync."""

# kalshi/errors.py:84-89
class KalshiSubscriptionError(KalshiWebSocketError):
    """Subscribe/unsubscribe request rejected by server."""

    def __init__(self, message: str, error_code: int | None = None) -> None:
        self.error_code = error_code
        super().__init__(message)

Recommended fix

Extend both classes with structured context populated at every raise site.

class KalshiSequenceGapError(KalshiWebSocketError):
    def __init__(
        self,
        message: str,
        *,
        channel: str | None = None,
        sid: int | None = None,
        client_id: int | None = None,
        last_seq: int | None = None,
        next_seq: int | None = None,
    ) -> None:
        self.channel = channel
        self.sid = sid
        self.client_id = client_id
        self.last_seq = last_seq
        self.next_seq = next_seq
        super().__init__(message)


class KalshiSubscriptionError(KalshiWebSocketError):
    def __init__(
        self,
        message: str,
        *,
        error_code: int | None = None,
        channel: str | None = None,
        client_id: int | None = None,
        op: Literal["subscribe", "unsubscribe", "update_subscription"] | None = None,
    ) -> None:
        self.error_code = error_code
        self.channel = channel
        self.client_id = client_id
        self.op = op
        super().__init__(message)

Update every raise site in kalshi/ws/ to populate the new fields. Update docs/errors.md so the "exposed for custom resync" framing is actually usable.

Backward-compatible: positional message and keyword error_code preserved.

Severity & category

medium / dx, ws

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestwsWebSocket-related

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions