From 36ad97337a4ac1ef85bd292509e0b717ca74e7b2 Mon Sep 17 00:00:00 2001 From: SandroHub013 Date: Thu, 7 May 2026 01:11:28 +0200 Subject: [PATCH] fix(kanban): treat dashboard event-stream cancellation as normal shutdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stopping `hermes dashboard` with Ctrl-C while the Kanban dashboard is open prints an ASGI traceback ending in `plugins/kanban/dashboard/plugin_api.py::stream_events` at the `asyncio.sleep(_EVENT_POLL_SECONDS)` line. This is a normal shutdown path: Uvicorn cancels the open websocket task while it is sleeping in the 300 ms poll loop. `asyncio.CancelledError` is a `BaseException` in Python 3.8+ — the bare `except Exception:` handler below the existing `WebSocketDisconnect:` clause does NOT catch it, so the cancellation surfaces as an application traceback and routine dashboard exit looks like a runtime failure. Add an explicit `except asyncio.CancelledError: return` clause beside the existing `WebSocketDisconnect` handler. Disconnection (client closed the tab) and shutdown cancellation (dashboard process exiting) are conceptually different paths but both warrant a quiet return; the two clauses are kept separate to keep that intent explicit. `asyncio` is already imported and used in this scope, so no new import is needed. The bare `except Exception:` handler is preserved verbatim, so genuine runtime failures still log a warning and close the socket cleanly. Closes #20790. --- plugins/kanban/dashboard/plugin_api.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/kanban/dashboard/plugin_api.py b/plugins/kanban/dashboard/plugin_api.py index 3176737a8c..f7dfd91a7d 100644 --- a/plugins/kanban/dashboard/plugin_api.py +++ b/plugins/kanban/dashboard/plugin_api.py @@ -1521,6 +1521,13 @@ async def stream_events(ws: WebSocket): await asyncio.sleep(_EVENT_POLL_SECONDS) except WebSocketDisconnect: return + except asyncio.CancelledError: + # Normal shutdown path: dashboard process exit (Ctrl-C) cancels the + # websocket task while it is sleeping in the poll loop. + # CancelledError is a BaseException in 3.8+ so the bare Exception + # handler below would not catch it; without this clause Uvicorn + # surfaces the cancellation as an application traceback. Quiet it. + return except Exception as exc: # defensive: never crash the dashboard worker log.warning("Kanban event stream error: %s", exc) try: