Reason Errors
Sometimes a single operation can fail in several related ways, and you want them
grouped under one error type rather than scattered across the error channel. A
reason error is a tagged error with a reason field whose value is itself a
union of tagged errors. The outer error names the operation; the inner reason
names exactly what went wrong.
import { Effect, Schema } from "effect"
// Each reason is an ordinary tagged error...class RateLimitError extends Schema.TaggedErrorClass<RateLimitError>()( "RateLimitError", { retryAfter: Schema.Number }) {}
class QuotaExceededError extends Schema.TaggedErrorClass<QuotaExceededError>()( "QuotaExceededError", { limit: Schema.Number }) {}
class SafetyBlockedError extends Schema.TaggedErrorClass<SafetyBlockedError>()( "SafetyBlockedError", { category: Schema.String }) {}
// ...and the outer error carries one of them in a `reason` field.class AiError extends Schema.TaggedErrorClass<AiError>()("AiError", { reason: Schema.Union([RateLimitError, QuotaExceededError, SafetyBlockedError])}) {}
declare const callModel: Effect.Effect<string, AiError>The error channel stays clean — callers see a single AiError — while the
reason preserves the full detail for anyone who wants to drill in. This is the
shape used throughout Effect’s own AI modules.
catchReason — handle one reason
Section titled “catchReason — handle one reason”Effect.catchReason takes the outer error tag, the reason tag to handle, a
handler for that reason, and an optional catch-all for the remaining reasons.
import { Effect } from "effect"// AiError and its reason errors (RateLimitError, QuotaExceededError,// SafetyBlockedError) are the ones declared in the first example above.
declare const callModel: Effect.Effect<string, AiError>
const handleOneReason = callModel.pipe( Effect.catchReason( "AiError", // the outer error _tag "RateLimitError", // the reason _tag to catch // handler for the caught reason — note `reason.retryAfter` is available (reason) => Effect.succeed(`Retry after ${reason.retryAfter} seconds`), // optional catch-all for the other reasons (reason) => Effect.succeed(`Model call failed for reason: ${reason._tag}`) ))catchReasons — a handler per reason
Section titled “catchReasons — a handler per reason”Effect.catchReasons handles several reasons of one error at once, keyed by
reason tag — the reason-level analogue of
catchTags.
import { Effect } from "effect"// AiError and its reason errors carry over from the first example above.
declare const callModel: Effect.Effect<string, AiError>
const handleMultipleReasons = callModel.pipe( Effect.catchReasons("AiError", { RateLimitError: (reason) => Effect.succeed(`Retry after ${reason.retryAfter} seconds`), QuotaExceededError: (reason) => Effect.succeed(`Quota exceeded at ${reason.limit} tokens`) // Any reasons you omit remain in the AiError that flows through. }))As with catchReason, you can pass a final catch-all handler as the third
argument to cover every remaining reason.
unwrapReason — promote reasons to the error channel
Section titled “unwrapReason — promote reasons to the error channel”If you would rather treat each reason as a first-class error, Effect.unwrapReason
flattens the reasons out of the wrapper and into the error channel. After
unwrapping you can use the ordinary catchTags
machinery.
import { Effect } from "effect"// AiError and its reason errors carry over from the first example above.
declare const callModel: Effect.Effect<string, AiError>
const unwrapAndHandle = callModel.pipe( // AiError disappears; RateLimitError | QuotaExceededError | SafetyBlockedError // now flow directly in the error channel. Effect.unwrapReason("AiError"), Effect.catchTags({ RateLimitError: (reason) => Effect.succeed(`Back off for ${reason.retryAfter} seconds`), QuotaExceededError: (reason) => Effect.succeed(`Increase quota beyond ${reason.limit}`), SafetyBlockedError: (reason) => Effect.succeed(`Blocked by safety category: ${reason.category}`) }))Next steps
Section titled “Next steps”- Build resilience around these errors with Fallback & Retry.
- See reason errors in practice in the AI modules.