Repository navigation
Description
Audit: 2026-05-21 (post-v2.4) multi-reviewer SDK sweep
This umbrella tracks the 34 issues opened from a parallel-reviewer audit of v2.4.0 spanning security/auth, HTTP transport + retry, WebSocket subsystem, REST resources, models + types + errors, performance hot paths, and public API surface + docs + testing.
Goal: harden the SDK against the workloads it markets to — real-time orderbook streaming, automated trading, integration into production trading systems. v2.4.0 (closure of audit #224) already shipped 33 fixes; this sweep covers the regressions and gaps that remained or were introduced by that work.
Reviewer outputs are captured in the orchestrator's agent:// artifacts (one per reviewer). All evidence is cross-referenced inline in each child issue with file:line + verbatim code excerpts.
Tier 1 — High (release-blocking class)
Money-risk, correctness, performance-cliff, or first-impression-quality defects on the hot paths. These should land before the next minor release.
- Config: demo=True + base_url override silently produces split REST/WS environment #239 — [high] Config: demo=True + base_url override silently produces split REST/WS environment
- Transport: httpx network errors (ConnectError/NetworkError/etc.) never retry, even for GET/HEAD/OPTIONS #240 — [high] Transport: httpx network errors (ConnectError/NetworkError/etc.) never retry, even for GET/HEAD/OPTIONS
- WS: validation failure on sequenced frame advances seq watermark — silent orderbook desync #241 — [high] WS: validation failure on sequenced frame advances seq watermark — silent orderbook desync
- orders.create() silently defaults count=1 and action="buy" on the kwarg path #242 — [high] orders.create() silently defaults count=1 and action="buy" on the kwarg path
- Orders: CreateOrderRequest.buy_max_cost validator accepts bool — silently becomes 1 cent #243 — [high] Orders: CreateOrderRequest.buy_max_cost validator accepts bool — silently becomes 1 cent
- WS perf: subscribe_book iterator re-materializes the entire orderbook on every delta #244 — [high] WS perf: subscribe_book iterator re-materializes the entire orderbook on every delta
- WS perf: per-frame asyncio.Task + asyncio.shield allocation in WS recv loop #245 — [high] WS perf: per-frame asyncio.Task + asyncio.shield allocation in WS recv loop
- Docs: migration guide is missing the v2.3 → v2.4 section that documents #194 batch breaking shape #246 — [high] Docs: migration guide is missing the v2.3 → v2.4 section that documents Orders: V1 batch_create / batch_cancel must return typed per-leg responses (partial-success defect) #194 batch breaking shape
- Docs: orders.md batch_create example crashes under the v2.4 return type #247 — [high] Docs: orders.md batch_create example crashes under the v2.4 return type
- Docs: README pagination quickstart references non-existent Page.has_more (should be has_next) #248 — [high] Docs: README pagination quickstart references non-existent Page.has_more (should be has_next)
Tier 2 — Medium (next minor)
Important correctness, consistency, security-defense-in-depth, and DX gaps.
- Auth: conflicting key inputs (constructor + env) silently prefer one without warning #249 — Auth: conflicting key inputs (constructor + env) silently prefer one without warning
- Config: unknown base_url host only warns — typo silently signs to attacker #250 — Config: unknown base_url host only warns — typo silently signs to attacker
- Errors: 408 and 504 do not map to KalshiTimeoutError #251 — Errors: 408 and 504 do not map to KalshiTimeoutError
- Errors: content-length cap on error body drops the typed-exception class #252 — Errors: content-length cap on error body drops the typed-exception class
- Transport: per-request extra_headers is unreachable through any resource method #253 — Transport: per-request extra_headers is unreachable through any resource method
- WS: resubscribe_one stash is never drained — frames captured during gap recovery are dropped #254 — WS: resubscribe_one stash is never drained — frames captured during gap recovery are dropped
- WS: stale orderbook frames mutate local book before subscription-existence check #255 — WS: stale orderbook frames mutate local book before subscription-existence check
- WS: KalshiBackpressureError lacks structured channel/sid/client_id fields #256 — WS: KalshiBackpressureError lacks structured channel/sid/client_id fields
- WS: _OrderbookIterator yields silently-empty Orderbook when manager has no book #257 — WS: _OrderbookIterator yields silently-empty Orderbook when manager has no book
- Models: WS payloads use raw str for _fp / _dollars fields (OrderGroup, Ticker) #258 — Models: WS payloads use raw str for _fp / _dollars fields (OrderGroup, Ticker)
- Models: Strike + fee-multiplier fields use bare Decimal/float, bypass #225 coercion #259 — Models: Strike + fee-multiplier fields use bare Decimal/float, bypass fix(types): preserve Decimal in DataFrames; positional serialization; reject bool #225 coercion
- Perf: REST has no pluggable JSON loader — orjson cannot speed up list parsing #260 — Perf: REST has no pluggable JSON loader — orjson cannot speed up list parsing
- Perf: _normalize_percent_encoding runs a regex on every signed path even when no % is present #261 — Perf: _normalize_percent_encoding runs a regex on every signed path even when no % is present
- Perf: header merge allocates a fresh 4-way dict per retry attempt #262 — Perf: header merge allocates a fresh 4-way dict per retry attempt
- Perf: snapshot apply walks the level list twice (validate tuples, then rebuild dict) #263 — Perf: snapshot apply walks the level list twice (validate tuples, then rebuild dict)
- Perf: Page.to_dataframe / to_polars goes through per-row Pydantic dumps #264 — Perf: Page.to_dataframe / to_polars goes through per-row Pydantic dumps
- Docs: breaking-change docstrings tag the change as v3.0.0 but it shipped in v2.4.0 #265 — Docs: breaking-change docstrings tag the change as v3.0.0 but it shipped in v2.4.0
- Types: KalshiClient.from_env(**kwargs: object) leaks Any-equivalent typing into a public constructor #266 — Types: KalshiClient.from_env(**kwargs: object) leaks Any-equivalent typing into a public constructor
Tier 3 — Polish bundles (opportunistic)
Each is a small atomic refactor; bundled to keep the issue tracker focused.
- Polish bundle: auth + transport small items (sign-executor race, Retry-After negative) #267 — Polish bundle: auth + transport small items (sign-executor race, Retry-After negative)
- Polish bundle: WS small items (cancelled mid-subscribe leak, snapshot empty-array default) #268 — Polish bundle: WS small items (cancelled mid-subscribe leak, snapshot empty-array default)
- Polish bundle: REST resource small items (positions *_all(), enum validation, deprecation, list helpers, stale comment) #269 — Polish bundle: REST resource small items (positions *_all(), enum validation, deprecation, list helpers, stale comment)
- Polish bundle: model small items (AwareDatetime in WS, V1 Literal, default=None, timestamp typing, Decimal NaN) #270 — Polish bundle: model small items (AwareDatetime in WS, V1 Literal, default=None, timestamp typing, Decimal NaN)
- Polish bundle: perf + testing small items (asyncio import, MessageQueue._size, http2 test, iterator/dataframe bench) #271 — Polish bundle: perf + testing small items (asyncio import, MessageQueue._size, http2 test, iterator/dataframe bench)
- Polish bundle: docs cleanup (stale ROADMAP entry, missing PyPI classifiers) #272 — Polish bundle: docs cleanup (stale ROADMAP entry, missing PyPI classifiers)
Suggested release plan
- Patch (v2.4.1) — Docs: README pagination quickstart references non-existent Page.has_more (should be has_next) #248 (README has_more), Docs: orders.md batch_create example crashes under the v2.4 return type #247 (batch_create example), Docs: migration guide is missing the v2.3 → v2.4 section that documents #194 batch breaking shape #246 (migration v2.3→v2.4), Docs: breaking-change docstrings tag the change as v3.0.0 but it shipped in v2.4.0 #265 (v3.0.0 docstring tags). Docs-only, ship same day.
- Patch (v2.4.2) — WS: validation failure on sequenced frame advances seq watermark — silent orderbook desync #241 (seq watermark advances on validation failure), Orders: CreateOrderRequest.buy_max_cost validator accepts bool — silently becomes 1 cent #243 (buy_max_cost bool), Config: demo=True + base_url override silently produces split REST/WS environment #239 (split REST/WS env), Transport: httpx network errors (ConnectError/NetworkError/etc.) never retry, even for GET/HEAD/OPTIONS #240 (network-error retry), orders.create() silently defaults count=1 and action="buy" on the kwarg path #242 (orders.create silent defaults). All money-risk fixes, non-breaking.
- Minor (v2.5.0) — bulk of Tier 1 perf (WS perf: subscribe_book iterator re-materializes the entire orderbook on every delta #244, WS perf: per-frame asyncio.Task + asyncio.shield allocation in WS recv loop #245) + Tier 2 (errors, WS reliability, model typing, perf). Most are non-breaking; Models: WS payloads use raw str for _fp / _dollars fields (OrderGroup, Ticker) #258 / Models: Strike + fee-multiplier fields use bare Decimal/float, bypass #225 coercion #259 / Polish bundle: model small items (AwareDatetime in WS, V1 Literal, default=None, timestamp typing, Decimal NaN) #270 widen a string field to Decimal which is technically breaking but caller-friendly.
- Polish bundles (Polish bundle: auth + transport small items (sign-executor race, Retry-After negative) #267–Polish bundle: docs cleanup (stale ROADMAP entry, missing PyPI classifiers) #272) — fold into v2.4.x / v2.5.0 wherever a bundle item naturally co-occurs with a tracked issue.
Surface coverage
| Surface | Tier 1 | Tier 2 | Tier 3 |
|---|---|---|---|
| Security / Auth | #239 | #249, #250 | #267 |
| HTTP Transport | #240 | #251, #252, #253 | #267 |
| WebSocket | #241, #244, #245 | #254, #255, #256, #257, #258 | #268 |
| REST Resources | #242 | — | #269 |
| Models / Types | #243 | #258, #259 | #270 |
| Performance hot paths | #244, #245 | #260, #261, #262, #263, #264 | #271 |
| Public API / Docs / Testing | #246, #247, #248 | #265, #266 | #271, #272 |
Source
Identified during the 2026-05-21 parallel-reviewer audit (7 subagents: security/auth, HTTP transport, WebSocket, REST resources, models + types, performance hot paths, public API + docs + testing).
Activity
TexasCoding commentedon May 21, 2026
All 34 child issues merged across 4 execution waves:
Verification on main: 2742 unit tests passing (3 skipped), mypy --strict clean, ruff clean.
Breaking changes folded in: #258, #259 (str→Decimal widening on
_fp/_dollarsand strike/fee fields), #242 (orders.create requires count+action). v2.5.0 minor bump justified.release: 2.5.0 — post-v2.4 multi-reviewer SDK audit closure (#273) (#294