Guides & concepts

Errors

Eight tags, closed union, exhaustive switch.

Every rejection from an endpoint is a member of HapiError. That includes a plain Error thrown by one of your own hooks, which is normalised to unknown with the original preserved as cause. Without that guard the union would be a lie and a switch could silently fall through.

TagMeansRetry?
httpThe server answered with a non-ok statussometimes
transportThe request never completed — offline, DNS, CORSyes
abortCancelled by a caller or a timeoutno
request-validationThe request body failed its schemanever
path-validationA path parameter failed its schemanever
response-validationThe response failed its schemanever
configurationA declaration is unusablenever
unknownAnything unforeseen, cause preservednever
errors.ts
switch (err.tag) {
  case 'http':
    if (err.status === 404) return null;
    throw err;
  case 'abort':
    return err.timedOut ? retry() : undefined;
  case 'response-validation':
    report('schema drift', err.rawResponse);
    return null;
  default:
    throw err;
}

Retry predicates can read the tag

Because the error channel is typed, a TanStack retry predicate can express things its own retry: 3 cannot — such as retrying a transport failure but never a 422.

useQuery(undefined, {
  retry: (n, err) => err.tag === 'transport' && n < 3,
});

onError is the terminal hook

It fires once for every failure that escapes, and receives a tagged error. It does not fire for aborts, and it does not fire when another hook supplied a fallback — nothing escaped.