Where
autobot-backend/services/npu_worker_manager.py
_load_workers_from_config (sync, line 141) calls self._save_workers_to_config() at line 146 without await.
_save_workers_to_config (line 167) is async def.
_load_workers_from_config itself only runs inside asyncio.to_thread(...) (call site: line 120), i.e. on a worker thread with no running event loop for the un-awaited coroutine to ever be scheduled on.
Impact
When the NPU worker config file is missing (first run / fresh deploy), line 146 is meant to create it by calling the save routine, but instead creates a coroutine object that is immediately garbage-collected — a silent no-op. Observed as:
RuntimeWarning: coroutine 'NPUWorkerManager._save_workers_to_config' was never awaited
self._save_workers_to_config()
Discovered via
Writing WS auth-reject tests for #12366 (autobot-backend/tests/test_websockets_auth_reject_12366.py::test_authenticated_ws_proceeds[/ws/npu-workers]) which exercises npu_workers_websocket_endpoint → _send_initial_worker_list → this code path with no config file present in the test sandbox.
Fix sketch
_load_workers_from_config needs to become async (or the save needs to be scheduled via asyncio.create_task/dispatched back onto the loop) so the coroutine is actually awaited — same pattern already used correctly at the other 4 call sites (lines 963, 997, 1068, 1379) which all await self._save_workers_to_config() from async contexts.
Unrelated to #12366's WS auth-handshake scope — filing as a fast-follow rather than bundling into that PR.
Where
autobot-backend/services/npu_worker_manager.py_load_workers_from_config(sync, line 141) callsself._save_workers_to_config()at line 146 withoutawait._save_workers_to_config(line 167) isasync def._load_workers_from_configitself only runs insideasyncio.to_thread(...)(call site: line 120), i.e. on a worker thread with no running event loop for the un-awaited coroutine to ever be scheduled on.Impact
When the NPU worker config file is missing (first run / fresh deploy), line 146 is meant to create it by calling the save routine, but instead creates a coroutine object that is immediately garbage-collected — a silent no-op. Observed as:
Discovered via
Writing WS auth-reject tests for
#12366(autobot-backend/tests/test_websockets_auth_reject_12366.py::test_authenticated_ws_proceeds[/ws/npu-workers]) which exercisesnpu_workers_websocket_endpoint→_send_initial_worker_list→ this code path with no config file present in the test sandbox.Fix sketch
_load_workers_from_configneeds to become async (or the save needs to be scheduled viaasyncio.create_task/dispatched back onto the loop) so the coroutine is actually awaited — same pattern already used correctly at the other 4 call sites (lines 963, 997, 1068, 1379) which allawait self._save_workers_to_config()from async contexts.Unrelated to #12366's WS auth-handshake scope — filing as a fast-follow rather than bundling into that PR.