| name | effect-errors-retries |
| description | Error modeling and recovery with TaggedError, catchTag(s), mapError, and retry schedules. Use when defining errors or adding resilience. |
| allowed-tools | Read, Grep, Glob, Edit, Write, mcp__effect-docs__effect_docs_search |
Errors & Retries
When to use
- You’re defining domain errors and recovery policies
- You need to map infrastructure errors to domain boundaries
- You must add retries, backoff, jitter, and timeouts
Model Errors
import { Data } from "effect"
class ValidationError extends Data.TaggedError("ValidationError")<{ field: string; reason: string }>{}
class NotFoundError extends Data.TaggedError("NotFoundError")<{ id: string }>{}
Map/Boundary
const repoCall = Effect.fail(new ValidationError({ field: "name", reason: "empty" }))
const service = repoCall.pipe(
Effect.mapError((e) => new ServiceError({ cause: e }))
)
Recover Precisely
program.pipe(
Effect.catchTags({
ValidationError: (e) => Effect.succeed(fix(e)),
NotFoundError: (e) => Effect.succeed(defaultValue)
})
)
Retry Policies
import { Schedule } from "effect"
const retry = Schedule.exponential("100 millis").pipe(
Schedule.jittered,
Schedule.compose(Schedule.recurs(5))
)
const resilient = program.pipe(Effect.retry(retry))
Timeouts & Races
const withTimeout = Effect.timeout(program, "2 seconds")
Guidance & Pitfalls
- Create separate TaggedError types per failure mode; avoid generic Error
- Map low-level (HTTP/DB) errors to domain errors at the boundary
- Retry only transient errors (use predicates with
Schedule.whileInput)
- Always combine retry with timeouts for bounded latency
- Prefer
catchTags over broad catchAll for clarity and safety
Real-world snippet: Map HTTP errors and retry selectively
const verify = http.get(url, { headers: { range: "bytes=0-0" } }).pipe(
Effect.flatMap(HttpClientResponse.filterStatus((s) => s < 400)),
Effect.catchIf(
(e) => e._tag === "ResponseError",
(cause) =>
cause.response.status < 500
? Effect.fail(new VideoInvalidError({ cause: "NotFound" }))
: Effect.fail(new ExternalLoomError({ cause: cause.response }))
),
Effect.retry({
schedule: Schedule.exponential("200 millis"),
times: 3,
while: (e) => e._tag !== "VideoInvalidError"
}),
.(, .)
)
Real-world snippet: Capture structured errors from Cause
import { Cause } from "effect"
const captureErrors = (cause: Cause.Cause<unknown>) => Effect.gen(function* () {
if (Cause.isInterruptedOnly(cause)) return { interrupted: true, errors: [] }
const raw = captureErrorsFrom(cause)
const errors = yield* Effect.forEach(raw, transformRawError({ stripCwd: true }))
return { interrupted: false, errors }
})
Quick Heuristics
- Domain errors → TaggedError per type
- Map low-level to high-level at boundaries
- Retry only transient errors; predicate with
Schedule.whileInput
Cross-links
Local Source Reference
CRITICAL: Search local Effect source before implementing
The full Effect source code is available at docs/effect-source/. Always search the actual implementation before writing Effect code.
Key Source Files
- Effect:
docs/effect-source/effect/src/Effect.ts
- Data:
docs/effect-source/effect/src/Data.ts
- Schedule:
docs/effect-source/effect/src/Schedule.ts
- Cause:
docs/effect-source/effect/src/Cause.ts
Example Searches
grep -F "catchTag" docs/effect-source/effect/src/Effect.ts
grep -F "catchAll" docs/effect-source/effect/src/Effect.ts
grep -F "mapError" docs/effect-source/effect/src/Effect.ts
grep -F "TaggedError" docs/effect-source/effect/src/Data.ts
grep -F "retry" docs/effect-source/effect/src/Schedule.ts
grep -F "exponential" docs/effect-source/effect/src/Schedule.ts
grep -F "jittered" docs/effect-source/effect/src/Schedule.ts
grep -F "isInterruptedOnly" docs/effect-source/effect/src/Cause.ts
Workflow
- Identify the error handling API you need (e.g., catchTag, retry)
- Search
docs/effect-source/effect/src/Effect.ts for the implementation
- Study the types and error recovery patterns
- Look at test files for usage examples
- Write your code based on real implementations
Real source code > documentation > assumptions
References