Skip to content
Docs menu / Unexpected Errors

Unexpected Errors

Unexpected errors, or defects, indicate bugs, violated invariants, or failures outside the program’s expected domain. They are retained in the runtime Cause, but do not appear in the typed error channel.

Defects normally should be reported and allowed to terminate the affected fiber. Recover from them only at boundaries where continuing is explicitly safe.

Creating a Defect

Effect.die(defect) creates an Effect that terminates with the supplied defect. Its typed error channel is never.

Example (Terminating on an Impossible Input)

import { Effect, Exit } from "effect"
const divide = (a: number, b: number) =>
b === 0
? Effect.die(new Error("Cannot divide by zero"))
: Effect.succeed(a / b)
const exit = Effect.runSyncExit(divide(1, 0))
Exit.isFailure(exit) && exit.cause.reasons[0]?._tag // => "Die"

Pass a string or, preferably, an Error with a useful message to Effect.die.

Exceptions thrown while evaluating Effect callbacks such as Effect.sync are also represented as defects.

Converting Typed Errors to Defects

Effect.orDie converts every typed failure into a defect and removes the typed error channel.

Example (Treating a Failure as Unrecoverable)

import { Effect, Exit } from "effect"
const program = Effect.fail(new Error("Invalid startup configuration")).pipe(
Effect.orDie,
)
const exit = Effect.runSyncExit(program)
Exit.isFailure(exit) && exit.cause.reasons[0]?._tag // => "Die"

To customize the defect, transform the typed error first with Effect.mapError and then apply Effect.orDie.

import { Cause, Effect, Exit, Predicate } from "effect"
const program = Effect.fail("missing token").pipe(
Effect.mapError((message) => new Error(`Startup failed: ${message}`)),
Effect.orDie,
)
const exit = Effect.runSyncExit(program)
const reason = Exit.isFailure(exit) ? exit.cause.reasons[0] : undefined
const message =
reason !== undefined &&
Cause.isDieReason(reason) &&
Predicate.isError(reason.defect)
? reason.defect.message
: undefined
message // => "Startup failed: missing token"

Inspecting the Complete Exit

Effect.exit moves the complete outcome into the success channel:

Effect<A, E, R> -> Effect<Exit<A, E>, never, R>

Unlike Effect.result, an Exit preserves the complete Cause, including defects and interruptions.

Example (Inspecting a Defect with Exit)

import { Cause, Effect, Exit } from "effect"
const exit = Effect.runSync(Effect.exit(Effect.die("boom")))
const hasDefect = Exit.isFailure(exit) && Cause.hasDies(exit.cause)
hasDefect // => true

This is useful at application boundaries, in tests, and when integrating with APIs that need an explicit value for every outcome.

catchDefect

Effect.catchDefect handles defects only. Typed failures and interruptions are left unchanged.

Example (Recovering from a Defect)

import { Effect, Predicate } from "effect"
const program = Effect.die(new Error("plugin crashed")).pipe(
Effect.catchDefect((defect) =>
Predicate.isError(defect)
? Effect.succeed(`disabled plugin: ${defect.message}`)
: Effect.die(defect),
),
)
Effect.runSync(program) // => "disabled plugin: plugin crashed"

catchCause

Effect.catchCause handles the complete Cause, including typed failures, defects, interruptions, and multiple reasons.

Example (Recovering Based on the Cause)

import { Cause, Effect } from "effect"
const program = Effect.die("boom").pipe(
Effect.catchCause((cause) =>
Cause.hasDies(cause)
? Effect.succeed("recovered at the boundary")
: Effect.failCause(cause),
),
)
Effect.runSync(program) // => "recovered at the boundary"

Prefer typed recovery operators such as Effect.catch and Effect.catchTag for domain errors. Use catchDefect or catchCause only where recovering from unexpected failures is intentional and safe.