# Tuning error behavior

Set what your bot does with an error through the errors block on your config. Covers replacing the card an unknown fault shows, the two ignore lists, and catching seedcord's own errors by code.

[`Seedcord`](https://docs.seedcord.org/packages/gateway/latest/seedcord) takes an `errors` block on its config. Every key in it is optional.

```ts title="src/bot.ts"
import { resolve } from 'node:path';

import { Seedcord } from '@seedcord/gateway';
import { GatewayIntentBits, RESTJSONErrorCodes } from 'discord.js';

export const seedcord = new Seedcord({
    bot,
    subscribers: { path: null },
    errors: {
        errorStack: true,
        logSilences: false,
        defaultError: BrandedFault,
        catchProcessErrors: false,
        ignoreApiCodes: [RESTJSONErrorCodes.UnknownMessage],
        ignoreEventApiCodes: [RESTJSONErrorCodes.UnknownMember]
    }
});
```

{/* prettier-ignore-start */}

| key                   | default           | what it changes                                                                                                       |
| --------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------- |
| `errorStack`          | `false`           | `true` prints the whole stack in your terminal for an unknown fault                                                   |
| `logSilences`         | `true`            | `false` drops the debug line written for a [`Silence`](https://docs.seedcord.org/packages/core/latest/silence) reason |
| `defaultError`        | the built-in card | the class seedcord renders for an unknown fault                                                                       |
| `catchProcessErrors`  | `true`            | `false` leaves the `unhandledRejection` and `uncaughtException` listeners to you                                      |
| `ignoreApiCodes`      | `[]`              | Discord api codes the interaction path swallows                                                                       |
| `ignoreEventApiCodes` | `[]`              | Discord api codes the event path swallows                                                                             |

{/* prettier-ignore-end */}

[Faults](/replying/faults) covers what `catchProcessErrors` turns off.

## Replacing the card

Every unknown fault shows the same generic card, asking the user to contact the person set in `notifications.developerUsername`. Replace it when your bot needs its own wording, or an action the user can take from the card.

`defaultError` takes a class that seedcord constructs with the fault's uuid and then renders. Subclass [`Notice`](https://docs.seedcord.org/packages/core/latest/notice) and build your own card.

```ts title="src/stops/BrandedFault.ts"
class BrandedCard extends BuilderComponent<'container'> {
    constructor(uuid: string) {
        super('container');

        this.instance.addTextDisplayComponents((text) =>
            text.setContent(`### Something broke\nCode \`${uuid}\``)
        );
    }
}

export class BrandedFault extends Notice {
    public constructor(private readonly uuid: string) {
        super(`unknown fault ${uuid}`);
    }

    public render(): ReplyResponse {
        return {
            components: [new BrandedCard(this.uuid).component]
        };
    }
}
```

> **Tip**
>
> Put a button on that card. The user can then file the fault themselves. The customId carries the uuid, so the click arrives with the code on it.
>
> ```ts title="src/handlers/ReportFault.ts"
> import { ButtonBuilder } from '@discordjs/builders';
> import {
>     ButtonHandler,
>     ButtonRoute,
>     CustomId,
>     RowComponent
> } from '@seedcord/gateway';
> import { ButtonStyle } from 'discord.js';
>
> export const ReportFault = new CustomId('report').uuid('uuid');
>
> export class ReportRow extends RowComponent<'button'> {
>     constructor(uuid: string) {
>         super('button');
>
>         this.instance.addComponents(
>             new ButtonBuilder()
>                 .setCustomId(ReportFault.encode({ uuid }))
>                 .setLabel('Report this')
>                 .setStyle(ButtonStyle.Secondary)
>         );
>     }
> }
>
> @ButtonRoute(ReportFault)
> export class OpenTicket extends ButtonHandler<
>     [typeof ReportFault]
> > {
>     public async execute(): Promise<void> {
>         await tickets.open(this.params.uuid);
>
>         await this.reply('Thanks. Someone will take a look.');
>     }
> }
> ```
>
> Add the row to `BrandedCard` through `addActionRowComponents`, passing it `new ReportRow(uuid).component`. [Custom ids](/components/custom-ids) explains how a customId carries a value like that uuid.

## The two ignore lists

Both ignore lists start empty, so every api code your own work throws reports as a fault. A real bug surfaces that way, like a double ack from a misplaced `defer()`. Add a code once you've confirmed it's an expected dead end for your bot. A swallowed code still writes a debug line.

Sending the fault card itself is a separate case. seedcord always drops a 10062, 40060, or 10008 that comes back from that send, whatever these lists hold. `UnknownInteraction` (10062) means the interaction expired. `InteractionHasAlreadyBeenAcknowledged` (40060) means something already answered it. `UnknownMessage` (10008) means the message the card would edit is gone.

> **Gateway only**
>
> `ignoreEventApiCodes` covers the event path, which only a gateway bot has. Discord delivers dead resources on events, like a reaction on a deleted message or a member who just left. Your handler's own fetch can throw on one. Until you add the code, each throw reports once a minute per handler. `UnknownMessage` is 10008, `UnknownMember` is 10007, and `UnknownUser` is 10013.

## Catching a framework error

When you wrap a seedcord call in `try`, branch on the error's code. The message wording can change in any release. A change to a code is always marked as breaking, so the changelog tells you when to update your check. Your transport package re-exports what you need from `@seedcord/errors`.

* [`SeedcordErrorCode`](https://docs.seedcord.org/packages/errors/latest/seedcord-error-code), every code the framework throws
* [`isSeedcordError`](https://docs.seedcord.org/packages/errors/latest/is-seedcord-error), the guard below
* [`paint`](https://docs.seedcord.org/packages/errors/latest/paint), the terminal colors seedcord logs with

Every error the framework throws carries one of those codes. `isSeedcordError` narrows a caught value to a seedcord error.

```ts
if (isSeedcordError(caught)) {
    logger.warn(`seedcord threw ${caught.code}`);
}

if (isSeedcordError(caught, 'SeedcordRangeError')) {
    const ranged = caught;
}

const taken = SeedcordErrorCode.CorePluginKeyExists;

if (isSeedcordError(caught, undefined, taken)) {
    const exact = caught.code;
}

const priority =
    SeedcordErrorCode.DecoratorInvalidMiddlewarePriority;

if (isSeedcordError(caught, 'SeedcordTypeError', priority)) {
    const both = caught;
}
```

The second argument narrows by class, one of `'SeedcordError'`, `'SeedcordTypeError'`, `'SeedcordRangeError'`, or `'SeedcordAggregateError'`. The third matches one exact code. Pass `undefined` as the second argument when the class doesn't matter, or pass both when you need the class and the code to match together.
