Changed config entry state transitions
Config entry state transitions when unloading and removing entries has been modified:
-
A new state
ConfigEntryState.UNLOAD_IN_PROGRESS
is added, which is set before calling the integration'sasync_unload_entry
Rationale:- Make it easier to write cleanup code which should run after the last config entry has been unloaded
- Improve debugging of issues related to reload and unload of config entries
-
The config entry state is set to
ConfigEntryState.FAILED_UNLOAD
when the integration'sasync_unload_entry
returns False
Rationale:- If
async_unload_entry
returnsFalse
, we can't assume the integration is still loaded, most likely it has partially unloaded itself, especially considering this is the pattern we recommend:
async def async_unload_entry(hass: HomeAssistant, entry: MyConfigEntry) -> bool:
"""Unload a config entry."""
# async_unload_platforms returns False if at least one platform did not unload
if (unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS)):
entry.runtime_data.listener()
# Finish cleanup not related to platforms
return unload_ok - If
-
The config entry is removed from
hass.config_entries
before calling the integration'sasync_remove_entry
is called
Rationale:- Make it easier to write cleanup code which should run after the last config entry has been removed
Custom integration authors need to review and update their integrations' async_unload_entry
and async_remove_entry
if needed.
The most common pattern which requires an update is this:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
loaded_entries = [
entry
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.state is ConfigEntryState.LOADED
]
if len(loaded_entries) == 1:
# The last config entry is being unloaded, release shared resources, unregister services etc.
...
This can now be simplified, if the custom integration's minimum Home Assistant version is set to 2025.3.0:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if not hass.config_entries.async_loaded_entries(DOMAIN):
# The last config entry is being unloaded, release shared resources, unregister services etc.
...
If the custom integration needs to be backwards compatible with previous releases of Home Assistant Core:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
other_loaded_entries = [
_entry
for _entry in hass.config_entries.async_loaded_entries(DOMAIN)
if _entry.entry_id != entry.entry_id
]
if not other_loaded_entries:
# The last config entry is being unloaded, release shared resources, unregister services etc.
...
Check the config entry documentation, and the home assistant core PR #138522 for additional background.