⚠️ Investigation, not a fresh confirmed reproduction. Reading from a user-supplied debug log against zha==1.3.1 / bellows==0.49.1 / zigpy==1.4.1 on Python 3.14.2, confirmed against the 1.3.1 tag.
home-assistant/core#172247 attached a full ZHA debug log covering one HA boot. The captured failure mode is a CancelledError from Home Assistant's STAGE_2_TIMEOUT (homeassistant/bootstrap.py:141, 300 s) cancelling ZHA's setup while it is inside bellows.zigbee.application.start_network → _restore_route_table → xncp_set_route_table_entry. The relevant log fragment:
2026-05-26 16:13:28.144 ERROR (MainThread) [homeassistant.config_entries] Setup of config entry '… ZBT-1 …' for zha integration cancelled
Traceback (most recent call last):
File "…/homeassistant/components/zha/__init__.py", line 188, in async_setup_entry
await zha_gateway.async_initialize()
File "…/zha/application/gateway.py", line 273, in async_initialize
await self._async_initialize()
File "…/zha/application/gateway.py", line 256, in _async_initialize
await self.application_controller.startup(auto_form=True)
…
File "…/bellows/zigbee/application.py", line 299, in start_network
await self._restore_route_table(backup.network_info.route_table)
…
asyncio.exceptions.CancelledError: Global task timeout: Bootstrap stage 2 timeout
The relevant async_initialize in zha/application/gateway.py:270-276:
async def async_initialize(self) -> None:
"""Initialize controller and connect radio."""
try:
await self._async_initialize()
except Exception:
await self.shutdown()
raise
asyncio.CancelledError is a subclass of BaseException (not Exception) since Python 3.8, so this except clause does not catch it. The cancellation propagates upward without self.shutdown() running, leaving the gateway half-initialised:
controller_event.set() happened earlier in start_network (at L244 in bellows 0.49.1), so is_controller_running later evaluates True at the bellows level.
_watchdog_task was started by zigpy.application.initialize at L217 and is never cancelled. In the linked debug log it continues to feed every ~10–15 s for the remaining ~5 minutes of capture.
_async_initialize never reaches load_devices() / load_groups() / add_listener(self) / platform setup — so the ZHA gateway is alive on the radio side but the entity layer is not wired up.
The user-visible symptom matches what home-assistant/core#172247 (and several comments in home-assistant/core#130548) describe: devices show up from the entity registry but commands fail. There is no recovery — the watchdog stays happy, so nothing triggers a reload.
Possible directions for a fix
Sketching, none committed:
- Broaden the handler so
shutdown() runs on cancellation too — e.g. except (Exception, asyncio.CancelledError): (or except BaseException: if also wanting to clean up on KeyboardInterrupt, with the usual caveats). Re-raising at the end preserves the cancellation up the stack.
- Alternative: wrap the cleanup in
try / finally instead of try / except / raise, so it always runs.
- Either way,
shutdown() needs to be safe to call mid-_async_initialize (it appears so: zigpy.application.shutdown() cancels _watchdog_task and tolerates missing optional state).
What I have not verified
Happy to fold in any additional context the maintainers have.
home-assistant/core#172247 attached a full ZHA debug log covering one HA boot. The captured failure mode is a
CancelledErrorfrom Home Assistant'sSTAGE_2_TIMEOUT(homeassistant/bootstrap.py:141, 300 s) cancelling ZHA's setup while it is insidebellows.zigbee.application.start_network→_restore_route_table→xncp_set_route_table_entry. The relevant log fragment:The relevant
async_initializeinzha/application/gateway.py:270-276:asyncio.CancelledErroris a subclass ofBaseException(notException) since Python 3.8, so thisexceptclause does not catch it. The cancellation propagates upward withoutself.shutdown()running, leaving the gateway half-initialised:controller_event.set()happened earlier instart_network(at L244 in bellows 0.49.1), sois_controller_runninglater evaluates True at the bellows level._watchdog_taskwas started byzigpy.application.initializeat L217 and is never cancelled. In the linked debug log it continues to feed every ~10–15 s for the remaining ~5 minutes of capture._async_initializenever reachesload_devices()/load_groups()/add_listener(self)/ platform setup — so the ZHA gateway is alive on the radio side but the entity layer is not wired up.The user-visible symptom matches what home-assistant/core#172247 (and several comments in home-assistant/core#130548) describe: devices show up from the entity registry but commands fail. There is no recovery — the watchdog stays happy, so nothing triggers a reload.
Possible directions for a fix
Sketching, none committed:
shutdown()runs on cancellation too — e.g.except (Exception, asyncio.CancelledError):(orexcept BaseException:if also wanting to clean up onKeyboardInterrupt, with the usual caveats). Re-raising at the end preserves the cancellation up the stack.try / finallyinstead oftry / except / raise, so it always runs.shutdown()needs to be safe to call mid-_async_initialize(it appears so:zigpy.application.shutdown()cancels_watchdog_taskand tolerates missing optional state).What I have not verified
send_packet/disconnectrace I filed at Possible race in send_packet: AttributeError on _ezsp.set_extended_timeout when disconnect() runs concurrently bellows#721, which has different symptoms).Happy to fold in any additional context the maintainers have.