Errors#

Actor errors are branded and narrow on a kind, so you can tell "the caller did something wrong" from "the cluster is having a moment" without string matching.

TypeScript
import { isActorError } from '@sigx/actors';

try {
    await actor(CartActor, id).checkout();
} catch (err) {
    if (isActorError(err) && err.kind === 'state-conflict') {
        // retry — another activation won a race and this one is stale
    }
}

The kinds#

ErrorkindWhat it meansWhat to do
ActorDeadlockErrordeadlockA call cycle re-entered a non-reentrant actor. Carries the full chain.Fix the cycle, or widen with reentrant. Not retryable.
ActorActivationErroractivationonActivate or migrateState threw.A bug or corrupt state. The stored record is untouched.
ActorStateConflictErrorstate-conflictAn etag CAS lost — another activation wrote first.The stale activation is faulted; the next call reloads. Usually retry once.
ActorMethodNotFoundErrormethod-not-foundNo such own key in methods:/streams:.Check the callable surface — a class instance is the usual cause.
ActorCallTimeoutErrorcall-timeoutThe call exceeded callTimeoutMs.See the precision note below.
ActorWrongHostErrorwrong-hostThe actor is placed on another host.Normally handled for you by proxy or redirect. See Locality routing.
ActorUnreachableErrorunreachableThe owning host could not be reached.Transient — a peer is down, restarting or partitioned. Retryable.
ActorStorageConflictRaised by an ActorStorage implementation on an etag mismatch.Surfaces as state-conflict. isStorageConflict() narrows it.
HostShutdownErrorhost-shutdownThe host is draining.Retry against the cluster, or let the client follow.

isActorError(err) narrows to ActorError; err.kind is ActorErrorKind. On a wrong-host, the error carries an ActorOwnerHint.

Deadlocks are immediate, on purpose#

ActorDeadlockError: Cart/user-42 → Pricing/eu → Cart/user-42

Every call carries its chain, so the cycle is detected at the moment it would block rather than surfacing as a timeout later. The alternative — a hang that resolves into ActorCallTimeoutError thirty seconds on — tells you almost nothing about the cause.

Call deadlines fire coarsely#

Deadlines 10 seconds or more away, which includes the 30-second default, share a one-second registry tick and may fire up to about two seconds late. They never fire early. Short budgets keep exact per-call timers.

If you are setting a deadline to bound user-visible latency, this is the contract to design against: a 30-second budget is "about 30 seconds", not a hard fence. Set callTimeoutMs: 0 to disable deadlines entirely.

Errors on the wire#

An actor endpoint is a synthesized server function, so error handling is core's: an ActorError crossing the wire carries its kind and status, and production masking applies exactly as it does for ServerFnError. A guard rejection answers with the status the guard threw.

For live subscriptions, failure is per subscription — a guard rejecting one widget produces an error frame at that index and the rest of the feed keeps streaming.

Next steps#