Skip to main content

Changes in OAuth 2.0 helper error handling

· 2 min read

Summary of changes

Starting as of 2026.3, we're enhancing how the OAuth 2.0 helper handles token request and refresh token failures. This change makes error handling more robust, decoupled from the aiohttp library and helps integrations, that utilize the Data Update Coordinator, to automatically trigger the right error handling.

What changes

When an OAuth 2.0 token request or token refresh failed, Home Assistant would allow the underlying aiohttp.ClientResponseError to propagate directly to the integration. This behavior is being changed and enhanced.

We're introducing three new exceptions that provide clearer semantics:

  • OAuth2TokenRequestTransientError - Recoverable errors, that can be retried.
  • OAuth2TokenRequestReauthError - Non-recoverable errors, that require a reauthentication.
  • OAuth2TokenRequestError - Base exception for when the above two criteria aren't met or to enable the integration to catch all token request exceptions.

Data Update Coordinator

Most integrations that use the OAuth 2.0 helper, also use the Data Update Coordinator. When a token request or refresh token fails, the exceptions will bubble up in the Data Update Coordinator and now triggers the following error handling:

For unrecoverable errors (400+, except 429 (rate limit)):

  • OAuth2TokenRequestReauthError: Data Update Coordinator raises ConfigEntryAuthFailed if exceptions should be raised or starts a reauthentication flow.

For transient errors (500+ and 429):

  • OAuth2TokenRequestTransientError: Data Update Coordinator treats it as an UpdateFailed and the retry mechanism will be triggered.

This means that integrations that use the OAuth 2.0 helper in combination with the Data Update Coordinator don’t need to do any special handling of the new exceptions.

Migration

Integrations that today use the OAuth 2.0 helper and handle aiohttp.ClientResponseError explicitly should adjust their error handling to deal with the new exceptions. To ease this transition, we have added a compatibility layer by having the new OAuth exceptions inherit from aiohttp.ClientResponseError. Existing code that catches this exception type should continue to work. It is however encouraged to refactor the code to use the new exceptions. See the code example for details.

Code example of migration

Update the exception handling and then continue to work out if it's a (non-)recoverable error in the integration. For example:

try:
await auth.async_get_access_token()
except OAuth2TokenRequestReauthError as err:
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN, translation_key="reauth_required"
) from err
except (OAuth2TokenRequestError, ClientError) as err:
raise ConfigEntryNotReady(
translation_domain=DOMAIN, translation_key="auth_server_error"
) from err

Reconfiguration support for webhook helper

· One min read

Integrations that use the webhook config flow helper (homeassistant.helpers.config_entry_flow.register_webhook_flow) now support reconfiguration. This allows the integration to retrieve the webhook again, or obtain an updated webhook when the Home Assistant instance URL changes.

Custom integrations using the webhook config flow helper must add translation strings for the reconfiguration flow.

Example translation strings for a reconfiguration flow:

{
"config": {
"abort": {
"reconfigure_successful": "**Reconfiguration was successful**\n\nIn Sleep as Android go to *Settings → Services → Automation → Webhooks* and update the webhook with the following URL:\n\n`{webhook_url}`"
},
"step": {
"reconfigure": {
"description": "Are you sure you want to re-configure the Sleep as Android integration?",
"title": "Reconfigure Sleep as Android"
}
}
}
}

For more details, see core PR #151729.

async_listen in Labs is deprecated

· One min read

The async_listen helper in the labs integration has been deprecated in favor of async_subscribe_preview_feature.

The new async_subscribe_preview_feature function provides a more consistent API, where the listener callback receives an EventLabsUpdatedData parameter containing the updated feature state. This eliminates the need to separately call async_is_preview_feature_enabled inside the listener to check the current value.

Old usage

from homeassistant.components.labs import async_is_preview_feature_enabled, async_listen

def my_listener() -> None:
if async_is_preview_feature_enabled(hass, DOMAIN, "my_feature"):
# feature enabled
...

async_listen(
hass,
domain=DOMAIN,
preview_feature="my_feature",
listener=my_listener,
)

New usage

from homeassistant.components.labs import EventLabsUpdatedData, async_subscribe_preview_feature

async def my_listener(event_data: EventLabsUpdatedData) -> None:
if event_data["enabled"]:
# feature enabled
...

async_subscribe_preview_feature(
hass,
domain=DOMAIN,
preview_feature="my_feature",
listener=my_listener,
)

Note that the new listener is a coroutine function and receives EventLabsUpdatedData as a parameter.

async_listen will be removed in Home Assistant 2027.3.

For more details, see core PR #162648.

Replacing pre-commit with prek

· One min read

By replacing pre-commit with prek we can increase the performance of our checks. Prek uses the same .pre-commit-config.yaml as pre-commit and is a complete replacement. Due to the fact that prek is written in Rust and allows the execution of different jobs in parallel, we can check our code even faster.

New development environments will automatically install prek and for existing ones please just update the test requirements by running uv pip install requirements_test.txt

Solving pyserial-asyncio blocking the event loop

· One min read

Summary of changes

Starting in 2026.7, installation of pyserial-asyncio will be blocked in Home Assistant.

Library maintainers and custom integrations are advised to migrate to pyserial-asyncio-fast.

Background

pyserial-asyncio blocks the event loop because it does a blocking sleep. The library is also not maintained so efforts to improve the situation haven't been released.

pyserial-asyncio-fast was created as a drop-in replacement (see the repository), and all core integrations have now been migrated.

Migration

pyserial-asyncio-fast was designed as a drop-in replacement of pyserial-asyncio, and the necessary changes are trivial.

Requirements

# Old
install_requires=[
"pyserial-asyncio"
]

# New
install_requires=[
"pyserial-asyncio-fast"
]

Usage

# Old
import serial_asyncio

async def connect():
conn = await serial_asyncio.open_serial_connection(**self.serial_settings)

# New
import serial_asyncio_fast

async def connect():
conn = await serial_asyncio_fast.open_serial_connection(**self.serial_settings)

More examples are available in the tracking pull request.

Serialization of Store data in worker thread is now opt-in

· 2 min read

The Store class from homeassistant/helpers/storage.py accepts a new constructor argument serialize_in_event_loop

If serialize_in_event_loop is True (the default):

  • The data_func passed to Store.async_delay_save is called from the event loop
  • Data produced by data_func passed to Store.async_delay_save is serialized to JSON in the event loop
  • Data passed to Store.async_save is serialized to JSON in the event loop

If serialize_in_event_loop is False:

  • The data_func passed to Store.async_delay_save is called from a separate thread, which means it must be thread safe and must not access the hass object
  • Data produced by data_func passed to Store.async_delay_save is serialized to JSON in a separate thread, which means it must be thread safe
  • Data passed to Store.async_save is serialized to JSON in a separate thread, which means it must be thread safe

The behavior has changed; data_func passed to Store.async_delay_save was previously always called from a separate thread and data produced by it or data passed to Store.async_save was previously always serialized by a separate thread.

The reason for the change is that it was not documented that data_func would be called by a thread other than the event loop or that JSON serialization would happen in a thread other than the event loop, and the data_func and data produced by it or passed to Store.async_save was generally not thread safe.

For more details, see core PR 157158 and core PR 157263.

Add a status callback for MQTT subscriptions

· One min read

Add a status callback for MQTT subscriptions

Integrations that use MQTT might need to wait for a subscription to complete before they initiate actions. The default behavior is that a subscription is queued and debounced, so callers usually do not wait for broker confirmation. Some integrations must guarantee that the broker finished the subscription.

The new mqtt.async_on_subscribe_done helper can be used to monitor MQTT subscriptions, to allow doing additional tasks. Make sure the same QoS is used as in the MQTT subscription.

Example:

from homeassistant.components import mqtt

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Setup integration MQTT subscription monitoring."""

def _on_subscribe_status() -> None:
"""Handle subscription ready signal."""
# Do stuff

# Handle subscription ready status update
await mqtt.async_on_subscribe_done(
hass,
"myintegration/status",
qos=1,
on_subscribe_status=_on_subscribe_status,
)

# Do stuff

Introducing Labs: Preview features before they become standard

· 3 min read

We're excited to announce a new system for shipping preview features in Home Assistant: Labs. Labs provides a standardized way for integrations to offer fully tested features that users can opt into before they become standard, allowing us to gather feedback and refine the design based on real-world usage.

What are Labs preview features?

Labs preview features are different from beta testing. While beta testing evaluates the stability of upcoming Home Assistant releases, Labs is about refining user interfaces and design. Labs features are fully tested and functional, but their design and behavior may still change as we gather real-world usage and feedback. This means they might have breaking changes, be extended with new functionality, or even be removed if they don't work out.

Think of it this way:

  • Beta: Evaluates the stability of upcoming Home Assistant releases
  • Labs: Fully tested features with evolving design and user interface, refined through real-world usage and feedback

How it works

Integrations declare preview features in their manifest.json with links for feedback, documentation, and issue reporting. Users can then enable these features in SettingsSystemLabs, and they activate immediately without requiring a restart. The integration code checks whether a feature is enabled and responds accordingly.

Why Labs?

Many of our most significant improvements benefit from real-world testing before becoming standard. Labs provides:

  1. Structured feedback channels: Each feature has dedicated URLs for feedback, documentation, and issue reporting
  2. Runtime activation: Features enable and disable instantly, no configuration updates or restart required
  3. Clear expectations: Users know they're trying fully tested features whose design may change based on feedback
  4. Iterative development: Integrate user feedback directly into the development process

Example: Kitchen Sink special repair

The Kitchen Sink demo integration includes a working example. When enabled, the "special repair" feature creates a repair issue to demonstrate how Labs features can interact with other Home Assistant integrations. See the developer documentation for the complete implementation.

Getting started

Ready to add a Labs preview feature to your integration? Check out our comprehensive guide which covers:

  • When to use Labs (and when not to)
  • How to define features in your manifest
  • Implementation patterns for backend and frontend features
  • Runtime activation requirements
  • Testing approaches
  • Feature lifecycle (preview → standard or removal)

What's next?

We encourage integration developers to consider Labs for:

  • Major UI changes or redesigns
  • Significant architectural changes that benefit from real-world testing
  • Features where user feedback will shape the final design

Labs is not for:

  • Permanent configuration options (use integration options instead)
  • Minor changes that can go directly into releases
  • Features with critical bugs or that are fundamentally incomplete

Try it yourself

Want to see Labs in action? Install the Kitchen Sink demo integration and enable the "Special repair" feature in Settings → System → Labs. You'll see firsthand how preview features work.