# Tuning error behavior

Set what your bot does with an error through the errors block on your config, like the card a fault shows and what it ignores.

Something threw inside a handler, and whoever ran the command got seedcord's generic card. The wording on that card is yours to replace. So is the decision about which errors reach a user at all, since a message someone already deleted is nothing they can act on.

You set both on the `errors` block [`Seedcord`](https://seedcord.org/docs/packages/gateway/latest/classes/seedcord) takes 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://seedcord.org/docs/packages/core/latest/classes/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](https://seedcord.org/guide/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://seedcord.org/docs/packages/core/latest/classes/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](https://seedcord.org/guide/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://seedcord.org/docs/packages/errors/latest/enums/seedcord-error-code), every code the framework throws
* [`isSeedcordError`](https://seedcord.org/docs/packages/errors/latest/functions/is-seedcord-error), the guard below
* [`paint`](https://seedcord.org/docs/packages/errors/latest/variables/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.
