Repairs
Tell users about problems they need to act on, and help them fix those problems with a repair flow.
A repair issue tells users about something that needs their attention, like a firmware that is too old, an API that is shutting down, or YAML configuration that is no longer supported. Issues show up under Settings > System > Repairs. Some only explain what to do, others can be fixed right there, with a repair flow.
Repairs are also how a deprecation that affects users is announced, which is one of their most common uses. Deprecations of core APIs are reported to developers in the log instead.
from homeassistant.helpers import issue_registry as ir
ir.async_create_issue(
hass,
DOMAIN,
"firmware_too_old",
is_fixable=False,
severity=ir.IssueSeverity.ERROR,
translation_key="firmware_too_old",
translation_placeholders={"version": device.firmware_version},
learn_more_url="https://www.home-assistant.io/integrations/example",
)| Argument | Default | What it is |
|---|---|---|
domain |
The domain that creates the issue | |
issue_id |
An ID for the issue, unique within the domain. Creating an issue with an existing ID updates it. | |
is_fixable |
Whether users can fix the issue with a repair flow | |
severity |
How bad it is. See below. | |
translation_key |
The key of the title and description in strings.json |
|
translation_placeholders |
None |
Values for the placeholders in the text |
config_entry_id |
None |
The config entry the issue belongs to. The issue is hidden while that entry is disabled, and deleted when the entry is removed. |
breaks_in_ha_version |
None |
The version in which something breaks, like "2027.4.0" |
learn_more_url |
None |
Where users find more information |
is_persistent |
False |
Whether the issue survives a restart. See persistence. |
issue_domain |
None |
Set when you create an issue on behalf of another integration |
data |
None |
Data for your repair flow. Not shown to users. |
| Severity | Use it when |
|---|---|
IssueSeverity.CRITICAL |
Reserved for true panic |
IssueSeverity.ERROR |
Something is broken now, and needs attention right away |
IssueSeverity.WARNING |
Something will break in the future, like an API shutting down |
The text goes in strings.json, under issues:
{
"issues": {
"firmware_too_old": {
"title": "The firmware of your Example device is too old",
"description": "Update the firmware from {version} in the Example app."
}
}
}A fixable issue has a fix_flow instead of a description, with the texts of the steps of its repair flow, the same way as a config flow:
{
"issues": {
"settings_outdated": {
"title": "Your Example settings need to be updated",
"fix_flow": {
"step": {
"confirm": {
"title": "Update the Example settings",
"description": "Select **Submit** to update your settings."
}
}
}
}
}
}When the problem is gone, delete the issue:
ir.async_delete_issue(hass, DOMAIN, "firmware_too_old")A typical pattern: check for the problem in async_setup_entry, and create or delete the issue based on the result.
For an issue with is_fixable=True, users get a Fix button. It starts a repair flow: a data entry flow, like a config flow. When the flow finishes with async_create_entry, the issue is removed.
If the fix is only a confirmation, you do not need any code: without a repairs.py, Home Assistant uses ConfirmRepairFlow, which asks the user to confirm and then removes the issue. The text of that confirmation is the confirm step in the fix_flow of the issue.
For anything else, create repairs.py with async_create_fix_flow, which returns the flow for an issue:
import probatio
from homeassistant.components.repairs import RepairsFlow, RepairsFlowResult
from homeassistant.core import HomeAssistant
class MigrateSettingsFlow(RepairsFlow):
"""Move the settings to the new format."""
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> RepairsFlowResult:
"""Start the fix."""
return await self.async_step_confirm()
async def async_step_confirm(
self, user_input: dict[str, str] | None = None
) -> RepairsFlowResult:
"""Ask the user to confirm, then fix it."""
if user_input is not None:
await async_migrate_settings(self.hass, self.data["entry_id"])
return self.async_create_entry(data={})
return self.async_show_form(
step_id="confirm", data_schema=probatio.Schema({})
)
async def async_create_fix_flow(
hass: HomeAssistant,
issue_id: str,
data: dict[str, str | int | float | None] | None,
) -> RepairsFlow:
"""Create the flow that fixes an issue."""
return MigrateSettingsFlow()Home Assistant sets issue_id and data on the flow from the issue. Do not pass them to the flow yourself: they would be overwritten.
Sometimes the fix is to reconfigure something, like the config entry or a subentry. A repair flow can hand over to that flow with next_flow:
from homeassistant.components.repairs import FlowType
from homeassistant.config_entries import SOURCE_RECONFIGURE
if user_input is not None:
result = await self.hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_RECONFIGURE,
"entry_id": self.data["entry_id"],
},
)
return self.async_abort(
reason="reconfigure",
next_flow=(FlowType.CONFIG_FLOW, result["flow_id"]),
)Aborting does not remove the issue. Delete it yourself once the fix is done, for example in the reconfigure step, right before async_update_reload_and_abort, or in async_setup_entry.
If the migration of a config entry failed and the user can fix the cause, call hass.config_entries.async_retry_migration(entry_id) from the repair flow once it is fixed. See migrate a config entry.
An issue stays until the integration that created it deletes it, or the user fixes it. What happens after a restart depends on is_persistent:
- Not persistent (the default): the issue is not shown after a restart, until your integration creates it again. Use this for problems you can check for, like an old firmware. Your integration checks again on the next start anyway.
- Persistent: the issue is shown again after a restart. Use this for problems you can only see when they happen, like a failed update.
Users can also ignore an issue. It stays ignored, also across restarts, until it is deleted and created again.