ruvnet/RuView · error · RuntimeError
SensingClient not connected. Use `async with` first.
Error message
SensingClient not connected. Use `async with` first.
What it means
SensingClient.stream() iterates self._ws, which is only assigned in __aenter__ (via websockets.connect) and reset to None by close(). Calling stream() on a client that never entered its `async with` block — or already exited it — raises RuntimeError with the usage hint.
Source
Thrown at python/wifi_densepose/client/ws.py:270
async def close(self) -> None:
"""Idempotent connection close."""
if self._ws is not None:
try:
await self._ws.close()
except Exception as e: # pragma: no cover — best-effort close
log.debug("ignored WS close error: %r", e)
self._ws = None
async def stream(self) -> AsyncIterator[SensingMessage]:
"""Yield typed messages until the server closes the connection
or the context is exited.
Decode failures on individual frames are logged at WARN and
swallowed — a malformed frame should not terminate the stream
(the next frame may be fine)."""
if self._ws is None:
raise RuntimeError("SensingClient not connected. Use `async with` first.")
try:
async for frame in self._ws:
if isinstance(frame, bytes):
frame = frame.decode("utf-8", errors="replace")
try:
yield _decode(frame)
except (ValueError, json.JSONDecodeError) as e:
log.warning("dropping malformed sensing-server frame: %r", e)
except ConnectionClosed:
# Graceful EOF — exit the iterator normally.
return
async def send_ping(self) -> None:
"""Send an application-level ping. The sensing-server replies
with `{"type": "pong"}` (main.rs:2698)."""
if self._ws is None:
raise RuntimeError("SensingClient not connected. Use `async with` first.")
await self._ws.send(json.dumps({"type": "ping"}))View on GitHub (pinned to 4685618388)
Solutions
- Wrap usage in the async context manager: `async with SensingClient(url) as client: ...`
- Only iterate stream() while the context is active; treat close() as terminal
- For reconnection, wrap the whole `async with` in an application-level retry loop (the client intentionally does not auto-reconnect)
Example fix
# before
client = SensingClient("ws://localhost:8765/ws/sensing")
async for msg in client.stream(): # RuntimeError: not connected
...
# after
async with SensingClient("ws://localhost:8765/ws/sensing") as client:
async for msg in client.stream():
... Defensive patterns
Strategy: validation
Validate before calling
def sensing_connected(client) -> bool:
"""True while inside an `async with` block (connection is open)."""
return client._ws is not None
# only stream while the context is active:
async with SensingClient(url) as client:
assert sensing_connected(client)
async for msg in client.stream():
... Try / catch
try:
async for msg in client.stream():
...
except RuntimeError as e:
if "not connected" in str(e):
raise RuntimeError(
"stream() must run inside `async with SensingClient(...)`"
) from e
raise Prevention
- Construct and use SensingClient within one function scope so the context manager cannot be skipped
- Never hold a reference past the `async with` block; close() is terminal
- Wrap the whole `async with` in a retry loop for reconnection instead of reusing a closed client
When it happens
Trigger: `client = SensingClient(url)` followed by `async for msg in client.stream():` without `async with client:`; or calling stream() after close()/__aexit__ set _ws back to None.
Common situations: Refactoring away from a manual connect() API; copy-pasting from synchronous examples; a task started before the context manager was entered.
Related errors
- An internal error occurred. Please try again later.
- Client {client_id} not found
- SensingClient requires the `websockets` package. Install wit
- Unknown topic: {topic}
- Invalid stream options: ${validationResult.errors.join(', ')
AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16).
Data as JSON: /api/errors/9b4249617dcb4902.
Report an issue: GitHub.