Skip to content

fix(ws): guard register_client() against event_bus=None on reconnect race (0.21.111)#1020

Merged
oeway merged 2 commits into
mainfrom
fix/0.21.111-reconnect-register-race
Jul 14, 2026
Merged

fix(ws): guard register_client() against event_bus=None on reconnect race (0.21.111)#1020
oeway merged 2 commits into
mainfrom
fix/0.21.111-reconnect-register-race

Conversation

@oeway

@oeway oeway commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Nightly-review hardening (mint-ram, 2026-07-14). The 0.21.107 send-after-close fix held (0/24h, no leak); this fixes one of two remaining low-volume WS teardown races.

Bug (caught, non-fatal, ~2/24h)

On a reconnect via reconnection-token immediately followed by a 1001 disconnect, the background register_client() task (scheduled by RedisRPCConnection.on_message to subscribe_to_client_events) could run just as disconnect() set self._event_bus = None:

core/__init__.py in register_client -> await self._event_bus.subscribe_to_client_events(...)
AttributeError: 'NoneType' object has no attribute 'subscribe_to_client_events'

Fix (hypha/core/__init__.py)

register_client() captures the bus into a local and returns if it's already None. The local capture also closes the window where a concurrent disconnect() nils self._event_bus mid-await. Same teardown-race class as the 0.21.106 filtered_handler None guard.

Test

tests/test_websocket_lifecycle_hardening.py::test_register_client_task_after_teardown_no_error — schedules register_client, nulls _event_bus before it runs, awaits it → must not raise (pre-fix: AttributeError). 4/4 hardening suite green.

The second race (Exception in ASGI application, 2/24h, ws upgrade via mcp→a2a→http) is held for the exact exception stack (requested from the reviewer) before touching that routing path — fast-follow if not folded in here.

🤖 Generated with Claude Code

…race (0.21.111)

Prod teardown race (nightly review, ~2/24h, caught/non-fatal): on a reconnect via
reconnection_token immediately followed by a 1001 disconnect, the background
register_client() task that RedisRPCConnection.on_message schedules could run just
as disconnect() set self._event_bus = None, so
`await self._event_bus.subscribe_to_client_events(...)` raised AttributeError:
'NoneType' object has no attribute subscribe_to_client_events.

Fix: register_client() captures self._event_bus into a local and returns if it's
already None; the local capture also closes the mid-await window where a concurrent
disconnect() nils the attribute. Same teardown-race class as the 0.21.106
filtered_handler None guard.

Test: tests/test_websocket_lifecycle_hardening.py::test_register_client_task_after_teardown_no_error
— schedule register_client, null _event_bus before it runs, await it → must not
raise (pre-fix: AttributeError). 4/4 hardening suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added bug Something isn't working documentation Improvements or additions to documentation labels Jul 14, 2026
…11 bug 2)

Second teardown race from the nightly review: if a client opens the ws and goes
away (code 1005, no close frame) before sending its auth payload, receive_text()
at websocket.py raised WebSocketDisconnect, which the existing
except asyncio.TimeoutError didn't catch → uvicorn logged "Exception in ASGI
application" (~2/24h, benign). Fix: catch WebSocketDisconnect on the auth read and
return cleanly (log DEBUG). Test: test_ws_disconnect_during_auth_handshake_no_error
drives the ASGI endpoint with a pre-auth disconnect and asserts it doesn't raise.

Also: CHANGELOG now explicitly calls out that 0.21.111 rolls up the 0.21.108-110
local-auth + embedded-login features (per ops request — conscious feature call,
not a silent ride-along).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@oeway
oeway merged commit 7b53349 into main Jul 14, 2026
7 checks passed
@oeway
oeway deleted the fix/0.21.111-reconnect-register-race branch July 14, 2026 02:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant