> ## Documentation Index
> Fetch the complete documentation index at: https://developers.reintersect.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> How Effect handlers fail, what each kind of failure does for the member, the tagged errors the SDK and the Reintersect API raise, and catchTag patterns for handling them.

> Reintersect apps are in developer preview. Names and limits can still change before general
> release.

On the Effect API, failures are values in the error channel. A handler's errors are inferred from
what it yields, recorded on the piece, and handled with `Effect.catchTag` like any other Effect.

## Typed failures in handlers

Define your failures with `Schema.TaggedError` and fail with `yield*`:

```ts src/tracker.ts theme={null}
import { Context, Effect, Schema } from "effect";
import type { TaskStatus } from "./schemas";

export class TaskLocked extends Schema.TaggedError<TaskLocked>()("TaskLocked", {
  id: Schema.String,
}) {}

export class Tracker extends Context.Tag("Tracker")<
  Tracker,
  { readonly move: (id: string, status: TaskStatus) => Effect.Effect<void, TaskLocked> }
>() {}
```

An action that yields `tracker.move` without handling it can fail with `TaskLocked`. So can a
generator that runs `return yield* new TaskLocked({ id })`.

## What a failure does

A failure you don't handle, or a defect, fails the piece. Its details stay in your process: the SDK
answers Reintersect with a generic message, such as `Action "move" failed.`, and nothing from your
error's fields.

| Where it fails | What happens |
| - | - |
| Block action | Reintersect keeps the block's previous state and tree and shows an error to the member who acted. |
| Block view | The render fails, and the delivery answers `500`. |
| Reference `resolve` | The SDK logs it and answers `unavailable` for that URL. Reintersect keeps the link as it is and tries again later. |
| Command or message action | The member sees a failure card with **Run again**. |
| Deferred answer | The SDK logs it and answers with a private `danger` notice. See [Deferred answers](/effect/deferring#when-the-deferred-effect-fails). |
| Event | The delivery fails, and Reintersect retries it with backoff. |
| Route | The response is a `500` with no details. |
| `waitUntil` task | The SDK logs it. Nothing else happens, because the delivery has already answered. |

## Turn a failure into an answer

When the member should know what went wrong, catch the failure and return a result. In an action,
that's a notice, and the block keeps its state:

```ts src/moveTask.ts theme={null}
import { defineAction } from "@reintersect/sdk/effect/app";
import { Effect } from "effect";
import { MoveTask, TaskState } from "./schemas";
import { Tracker } from "./tracker";

export const moveTask = defineAction(
  "move",
  { args: MoveTask, state: TaskState, description: "Move the task to another status" },
  function* ({ args }) {
    const tracker = yield* Tracker;

    return yield* tracker.move(args.id, args.status).pipe(
      Effect.as({ state: { status: args.status } }),
      Effect.catchTag("TaskLocked", () =>
        Effect.succeed({ notice: { tone: "warning" as const, text: "That task is locked" } }),
      ),
    );
  },
);
```

Use `fieldErrors` for form validation the schema can't express, and `Effect.catchTags` to handle
several tags at once. In a command or message action, return a notice in the same way. A `danger` or
`warning` notice on its own ends the run as failed and shows your text on the failure card.

## Errors from the client

The client's methods put the Reintersect API's errors in the error channel, so they show up in your
handler's type until you handle them:

```ts theme={null}
yield* reintersect.blocks
  .update(TaskBlock, blockId, { state: { status: "done" }, expectedVersion: 4 })
  .pipe(
    Effect.catchTags({
      ReintersectAppBlockVersionConflictError: () => Effect.void,
      ReintersectAppBlockFinalError: () => Effect.void,
    }),
  );
```

Each method's error union is exported from `@reintersect/sdk/effect`, such as
`ReintersectBlocksUpdateError`. A union holds:

* The endpoint's API errors, such as `ReintersectAppBlockVersionConflictError`,
  `ReintersectAppBlockNotFoundError` or `ReintersectAppInteractionExpiredError`. The
  [error reference](/api-reference/errors) lists every tag with its status and fields.
* Transport errors: `ReintersectSdkNetworkError`, `ReintersectSdkTimeoutError`,
  `ReintersectSdkInvalidResponseError` and `ReintersectSdkInvalidInputError`.
* Token errors, when the client mints its own install token: `ReintersectAppInstallTokenError`,
  `ReintersectAppConfigurationError`, `ReintersectAppInstallNotFoundError`,
  `ReintersectAppInstallRevokedError` and `ReintersectApiRateLimitExceededError`.

Block state is checked on both sides. State you pass that doesn't match the block's schema fails
with `ReintersectSdkInvalidInputError` before any request, and state that comes back without
decoding fails with `ReintersectSdkInvalidResponseError`.

Reads (`get`, `query` and `revalidate`) are retried twice with jittered backoff on network errors,
timeouts and `503`. Writes run once. To retry a write, compose `Effect.retry` with a bounded,
jittered schedule yourself, and only where running it twice is safe, such as an update with
`expectedVersion`.

## Errors in a delivery

These errors describe a delivery that the SDK couldn't run. They're exported from
`@reintersect/sdk/effect/app` and become the delivery's response. They aren't in your handlers'
error channels.

| Error | Status | Code | When |
| - | - | - | - |
| `ReintersectAppSignatureError` | `401` | `signature_invalid` | The delivery's signature headers are missing, expired or wrong. |
| `ReintersectAppDeliveryInvalidError` | `400`, or `413` when too large | `delivery_invalid` | The body isn't a delivery this SDK accepts. |
| `ReintersectAppPieceUnknownError` | `422` | `unknown_block`, `unknown_action` and so on | The delivery names a piece the app doesn't have, often after a manifest change. |
| `ReintersectAppSchemaRejectedError` | `422`, or `500` for `config`, `env` and `result` | `invalid_props`, `invalid_state` and so on | A value failed its schema. `result` means your handler returned something its schema rejects. |
| `ReintersectAppConfigurationError` | `500` | `configuration` | An environment variable the app needs isn't set or isn't valid, such as the signing secret, or the app's services failed to start. |
| `ReintersectAppHandlerFailedError` | `500` | `handler_failed` | A handler failed. The message names the handler, not the cause. |
| `ReintersectAppViewInvalidError` | `500` | `render_failed` | A view returned a tree the catalog rejects. |

The [testing kit](/effect/testing) raises its own tagged errors, such as
`ReintersectTestControlMissingError`, so a test can assert on a failure.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.