Skip to content

Action errors

An action that doesn’t happen is never silent. Whether the hook refused it before sending (the entity is unavailable, the value is out of range) or Home Assistant refused it, it becomes an HaActionError, and the hook’s onActionError option hears of it.

code category retryable Means
not_connected connection yes The store wasn’t connected, so the call never reached Home Assistant.
service_failed rejected yes Home Assistant refused the call.
transport_unknown connection no The connection changed during the call, so it may or may not have happened. A repeat could do it twice.
unsupported unsupported no The entity, or the transport, can’t do it: an unavailable light, a cover without a stop.
invalid_input invalid_input no The value was wrong before it was sent: a temperature out of range, a brightness that isn’t a number.

retryable means a repeat is safe on the current connection, not that it will work.

codeHaActionErrorCode
One of the five codes above. Choose your words by this.
categoryHaActionErrorCategory
The code's family.
retryableboolean
Whether repeating the same call is safe on this connection.
domain?string
The service's domain, when known.
service?string
The service, when known.
entityId?string
The entity the action was for, when known.
translationKey?string
Sanitized provider translation key, surfaced only as a string scalar.
providerCode?string
Sanitized provider code, surfaced only as a string scalar.

An error never carries Home Assistant’s own message or payload, which could echo an alarm code. Use code, not message: the message’s wording can change.

import { useState } from "react";
import { useHaLight } from "@dashhaus/core";
import type { HaActionError } from "@dashhaus/core";
/** Words for a refused or failed action, chosen by its code, never by its message. */
function explain(error: HaActionError): string {
switch (error.code) {
case "not_connected":
return "Not connected to Home Assistant. Try again in a moment.";
case "service_failed":
return "Home Assistant refused that.";
case "transport_unknown":
return "The connection dropped mid-way, so it may or may not have happened.";
case "unsupported":
return "This light can't do that.";
case "invalid_input":
return "That value is out of range.";
}
}
export function LampWithErrors({ entityId }: { entityId: string }) {
const [problem, setProblem] = useState<string>();
const { entity, actions } = useHaLight(entityId, { onActionError: (error) => setProblem(explain(error)) });
return (
<div>
<button type="button" aria-pressed={entity.state === "on"} onClick={actions.onToggle}>
{entity.name}
</button>
{problem && <p role="alert">{problem}</p>}
</div>
);
}

The store keeps the latest failed action as lastActionError in its snapshot, whichever control made it. It keeps the call to offer again only when a repeat is safe, and never keeps a call that carries an alarm code.

import { useHaSelector, useHaStore } from "@dashhaus/core";
/**
* One bar for the whole dashboard: the store keeps the latest failed action, and offers it again only when a repeat
* is safe on this connection (an alarm code is never kept).
*/
export function RetryBar() {
const store = useHaStore();
const error = useHaSelector((snapshot) => snapshot.lastActionError);
if (!error) return null;
return (
<div role="alert">
{error.domain ? `${error.domain}.${error.service ?? "call"} failed.` : "An action failed."}
{error.retryable && store.canRetryLastAction?.() && (
<button type="button" onClick={() => void store.retryLastAction?.().catch(() => undefined)}>
Try again
</button>
)}
<button type="button" onClick={() => store.clearActionError?.()}>
Dismiss
</button>
</div>
);
}

A successful call means Home Assistant accepted the command, not that the device did it. The entity’s next state is the answer: a light that didn’t turn on still shows off.

onArm and onDisarm answer instead of only reporting: they resolve true when Home Assistant took the call and false for a wrong code, which isn’t an error. Any other failure goes to onActionError and rejects the promise. Alarm shows all three.

A failed background read, such as a forecast or the registries, is never an action error. A forecast failure goes to the weather hook’s onForecastError; useHaRequest returns it as error; the history and registry hooks read as undefined.