useOnRpcError

One place to react to rpc failures that are nobody's local problem — auth loss, telemetry, toasts.

Some failures do not belong to the component that triggered them. An Unauthenticated error means the session is gone, whatever screen you happen to be on. Rather than repeat that check in every useMutation call site, each api's runtime carries an error bus; this hook subscribes to it with React lifecycle.

import { useOnRpcError } from '@voltro/client'
import { errorTag } from '@voltro/protocol'

useOnRpcError('app', useCallback((e) => {
  if (errorTag(e.error) === 'Unauthenticated') redirectToSignIn()
}, []))

The event is { source, tag, error, traceId? }. source is 'mutation' | 'action' | 'subscription', so both unary calls and stream failures arrive here. error is the raw value — a Schema.TaggedError, a plain Error, or anything else that was thrown; errorTag(err) reads _tag off tagged errors and returns undefined otherwise. traceId is the same id the server logged, so a handler can point at voltro logs --trace <id>.

Two things to know. The hook re-subscribes whenever the listener reference changes, so define it with useCallback or at module scope unless you want that. And the bus is per api runtime — an app talking to several apis subscribes once per api name.

You do not need this hook to REPORT rpc failures. Every event this bus emits is also published on the client error bus, so a reporter — the Sentry browser integration, or anything wired with subscribeClientErrors — already sees it, under source: 'rpc.mutation' | 'rpc.action' | 'rpc.subscription' with the rpc tag and traceId in context. This hook is for cross-cutting policy (redirect on Unauthenticated, toast on a network failure), not for telemetry; bridging the two by hand now reports twice.

Everything is published, including a reconnect storm. If that is too loud for your reporter, gate it in the subscriber — which failures are worth an event is your policy, and the bus cannot hold one for every subscriber.

This is the read side of failures that already happened on the wire. To push a client-side error the server never saw, call reportClientError(error, context) instead. Note also that a subscription failure after data arrived reaches only this bus, never the hook's error field — see Queries.