Repository navigation
Description
Summary
KalshiClient.close() (sync and async) unconditionally calls self._auth.close(). from_env() constructs KalshiAuth locally so ownership is unambiguous, but the public auth=... ctor kwarg is explicitly documented for sharing — a user can build one KalshiAuth and hand it to multiple clients (REST + async REST + WS).
Closing any one of those clients calls ThreadPoolExecutor.shutdown(wait=False) and nulls _sign_executor. The next sign_request_async on a sibling client lazily re-creates the executor (so signs eventually succeed), but any sign already in flight on the old executor races with shutdown — submit after shutdown raises RuntimeError: cannot schedule new futures after shutdown and propagates up through the async transport's retry loop.
This is a latent foot-gun for any deployment that pairs KalshiClient + AsyncKalshiClient + KalshiWebSocket off one KalshiAuth (a documented pattern in docs/authentication.md).
Separately, KalshiAuth.close() itself sets _sign_executor = None then the lazy-init in _get_sign_executor silently recreates the executor on next use. So close() is not idempotent in the close-once sense — it's actually "shutdown + restart on demand".
Location
kalshi/client.py:140-144—KalshiClient.close()kalshi/async_client.py:171-175—AsyncKalshiClient.close()kalshi/auth.py:260-271—KalshiAuth.close()(and the lazy re-init in_get_sign_executor)
Evidence
# kalshi/client.py:140-144
def close(self) -> None:
self._transport.close()
if self._auth is not None:
self._auth.close() # ← closes executor even if auth was provided externally# kalshi/auth.py:268-271
with self._sign_executor_lock:
if self._sign_executor is not None:
self._sign_executor.shutdown(wait=False)
self._sign_executor = None # ← next sign_request_async() recreatesRecommended fix
Part 1 — ownership-aware close
In KalshiClient.__init__, track whether the auth was provided externally:
self._auth_owned = auth is None # True only when we constructed it from kwargs/env
# In close():
if self._auth is not None and self._auth_owned:
self._auth.close()Document the ownership contract in KalshiClient.__init__ and KalshiAuth docstrings.
Part 2 — make close() terminal
In KalshiAuth, add a _closed: bool = False flag. Set in close(). In _get_sign_executor, raise:
if self._closed:
raise RuntimeError(
"KalshiAuth has been closed; create a new instance to sign further requests"
)Document close() as terminal in the docstring.
Tests:
test_close_does_not_shut_externally_provided_authtest_close_shuts_locally_constructed_authtest_closed_auth_raises_on_sign_request_async
Severity & category
medium / concurrency, dx
Activity
polish: cursor guard O(1); batch bytes path; bench scripts; close ter…