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.
| Tag | Means | Retry? |
|---|---|---|
| http | The server answered with a non-ok status | sometimes |
| transport | The request never completed — offline, DNS, CORS | yes |
| abort | Cancelled by a caller or a timeout | no |
| request-validation | The request body failed its schema | never |
| path-validation | A path parameter failed its schema | never |
| response-validation | The response failed its schema | never |
| configuration | A declaration is unusable | never |
| unknown | Anything unforeseen, cause preserved | never |
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.