Skip to content
Agentmart
Submit a tool

effect-errors

Effect error handling, for expected errors, defects and interruptions.

Skill by andabove

IndexedMIT
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/andabove-effect-errors

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Effect error handling, for expected errors, defects and interruptions. Use when you define or raise a tagged error, recover with `Effect.catch*`, read a `Cause` or `Exit`, set a retry or timeout policy, or decide whether a failure is an error or a defect.

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/effect-errors

What's inside

Skill name
effect-errors
Description
Effect error handling, for expected errors, defects and interruptions. Use when you define or raise a tagged error, recover with `Effect.catch*`, read a `Cause` or `Exit`, set a retry or timeout policy, or decide whether a failure is an error or a defect.
License
MIT
Instructions
236 lines in SKILL.md · view on GitHub

Files (4)

  • SKILL.md
  • effect-errors-cause-and-exit.md · references/
  • effect-errors-operators.md · references/
  • effect-errors-retry-timeout.md · references/

SKILL.md (first lines)

# Effect errors

Checked against `effect` 4.0.0. When the project has a newer version, check each API in `node_modules/effect/src/Effect.ts`, `Cause.ts` and `Exit.ts`, and read `node_modules/effect/AGENTS.md`. Load the `effect` skill too: it covers creating effects, `Effect.gen` and running.

## Three ways an effect can end badly

| Kind | What it means | Where it is | Typical source |
| --- | --- | --- | --- |
| **Failure** (expected error) | part of the domain: invalid input, not found, rate limited | the `E` in `Effect<A, E, R>` | `Effect.fail`, `yield* new SomeError(...)`, `Effect.try` with `catch` |
| **Defect** | a bug or a broken invariant | only in the `Cause`, as a `Die` reason | a throw in `Effect.sync`, a rejection in `Effect.promise`, `Effect.die`, `Effect.orDie` |
| **Interruption** | someone stopped the fiber | only in the `Cause`, as an `Interrupt` reason | a timeout, a lost race, `SIGINT` under `runMain`, `Fiber.interrupt` |

Decide by the caller: if a caller can do something useful about it (retry, fall back, show a message, map to an HTTP status), make it a failure in `E`. If no caller can, make it a defect and let it reach the edge, where it is logged.

## Define errors as tagged classes

```ts
import * as Data from "effect/Data"
import * as Effect from "effect/Effect"
import * as Schema from "effect/Schema"

export class UserNotFound extends Schema.TaggedError<UserNotFound>()("UserNotFound", {
  userId: Schema.String,
  message: Schema.String
}) {}

export class StoreUnavailable extends Data.TaggedError("StoreUnavailable")<{
  readonly cause: unknown
}> {
  override get message() {
    return "user store is unavailable"
  }
}

declare const queryUser: (id: string) => Effect.Effect<{ readonly name: string } | undefined, StoreUnavailable>

export const findUser = Effect.fn("findUser")(function*(userId: string) {
  const user = yield* queryUser(userId)
  if (user === undefined) {
    return yield* new UserNotFound({ userId, message: `no user with id ${userId}` })
  }
  return user
})
```

- Use `Schema.TaggedError` when the error crosses a boundary that encodes it: an HTTP API, RPC, a workflow, a queue, a log sink that needs JSON. Use `Data.TaggedError` for errors that stay in the process. `effect/Data` loads 15 modules, `effect/Schema` 113.
- Give each error a readable `message`: a `message` field, or a `get message()` override. Without one, `error.message` is the empty string and the logs say only the tag.
- Keep the original failure in a `cause` field (`cause: Schema.Defect()` in a schema error, `cause: unknown` in a data error). In a `Data.TaggedError` the field becomes the standard `Error.cause`.
- Make each `_tag` unique in the codebase. `catchTag` matches by the string, so two classes with one tag catch each other.
- Raise with `return yield* new UserNotFound({ ... })` in a generator, or `Effect.fail(new UserNotFound({ ... }))` in a pipeline. A tagged error instance is itself yieldable.
- Do not use strings or plain `Error` as `E`: callers cannot tell them apart with `catchTag`.

## Keep the error type honest

A declared error type can hide a missing case. A narrower error type fits a wider declared one, so when a change drops a case (a `NotFound` becomes a generic `StoreError`, and a 404 becomes a 500), a function declared with `Effect.fn.Return<A, NotFound | StoreError>` still compiles, and so does every caller.

- Let `Effect.fn` infer the error type from the body.
- Pin each public signature with a type test, `expectTypeOf` in a `*.test-d.ts` file. `toEqualTypeOf` fails when the inferred union loses a case or gains one.

```ts
import * as Data from "effect/Data"
import * as Effect from "effect/Effect"
import { expectTypeOf } from "vitest"

class NotFound extends Data.TaggedError("NotFound")<{ readonly id: string }> {}
class StoreError extends Data.TaggedError("StoreError")<{ readonly cause: unknown }> {}

interface Item { readonly id: string }
declare const query: (id: string) => Effect.Effect<Item | undefined, StoreError>

// No declared return type: the error type comes from the body.
export const getItem = Effect.fn("getItem")(function*(id: string) {
  const row = yield* query(id)
  if (row === undefined) return yield* new NotFound({ id })
  return row
})

// getItem.test-d.ts: fails to compile when the body stops raising NotFound.
expectTypeOf(getItem).returns.toEqualTypeOf<Effect.Effect<Item, NotFound | StoreError>>()
```

Checked with `tsc` on 4.0.0. With `NotFound` replaced by `StoreError` in the body, the declared form compiles, and the type test fails with `TS2344`. See `effect-testing` for running type tests.

## Recover

Pick the narrowest operator. Each one removes what it handles from `E`.

| You want to | Write |
| --- | --- |
| handle one tagged error | `Effect.catchTag("UserNotFound", (e) => ...)` |
| handle several tags with one handler | `Effect.catchTag(["UserNotFound", "Forbidden"], (e) => ...)` |
| handle several tags, each its own way | `Effect.catchTags({ UserNotFound: (e) => ..., Forbidden: (e) => ... })` |
| handle errors that match a predicate | `Effect.catchIf((e) => ..., (e) => ...)` |
| handle every expected error | `Effect.catch((e) => ...)` |
| handle one nested `reason` of a tagged error | `Effect.catchReason("AiError", "RateLimited", (r) => ...)` |
| replace any error with a value | `Effect.orElseSucceed(() => fallback)` |
| turn the error into a value | `Effect.result` (gives a `Result`), `Effect.option` (drops the error) |
| change the error type | `Effect.mapError((e) => new OtherError({ cause: e }))` |
| observe without handling | `Effect.tapError`, `Effect.tapErrorTag`, `Effect.tapCause` |
| give up: make it a defect | `Effect.orDie` |

What each operator sees when the cause holds one reason (proved against 4.0.0; for a cause with a failure and a defect, see **Mixed causes** below):

| Operator | Failure | Defect | Interruption |
| --- | --- | --- | --- |
| `catch`, `catchTag(s)`, `catchIf`, `result`, `option`, `match`, `ignore`, `orElseSucceed`, `retry`, `tapError` | yes | no | no |
| `catchDefect`, `tapDefect` | no | yes | no |
| `catchCause`, `matchCause`, `exit`, `sandbox`, `ignoreCause`, `tapCause`, `onError` | yes | yes | yes |

Traps:

- `Effect.catch` does not catch a defect on its own. A throw inside `Effect.sync` or a rejection inside `Effect.promise` passes through every typed handler. Wrap code that can throw with `Effect.try` or `Effect.tryPromise` and a `catch`.
- `Effect.ignore` and `Effect.result` keep a defect that comes alone. `Effect.ignoreCause` drops it; use it only where losing a bug report is acceptable.
- `catchCause` and `matchCause` also see an interruption that the effect raised itself (`Effect.interrupt`). If you recover from every cause, check `Cause.hasInterruptsOnly(cause)` first and re-raise it with `Effect.failCause(cause)`. An interruption from outside the fiber cannot be caught: the handler does not run.
- `try`/`catch` in an `Effect.gen` body does not see a failed effect (see the `effect` skill). Use the operators above.
- Recover from defects (`catchDefect`, `catchCause`) only at a boundary: a request handler that must answer 500, a plugin host, a worker loop that must survive one bad job.

### Mixed causes

From andabove/skills, MIT.

More from andabove/skills

Signals

Stars
0
Forks
0
Open issues
0
Last commit (this folder)
Oct 3, 2026 (26 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from andabove

Cite this page

effect-errors skill. Agent Mart. https://agentmart.dev/skills/andabove-effect-errors (last updated 2026-10-24)