# Faults

A throw your handler doesn't catch still owes the user an answer. Covers the card every fault renders, anything else you throw, stopping without a reply, and throws outside any handler.

Some calls fail in ways the user can't fix, like a database write. Catch the error and throw a [`Fault`](https://docs.seedcord.org/packages/core/latest/fault). The user gets a generic card with a tracking id, and the error you caught goes to your fault report along with the command and the user who ran it.

```ts title="src/handlers/Ban.ts"
import { Fault, SlashHandler, SlashRoute } from '@seedcord/gateway';

@SlashRoute('ban')
export class Ban extends SlashHandler<'ban'> {
    public async execute(): Promise<void> {
        const target = this.options.getUser('target');

        try {
            await cases.open(target.id);
        } catch (cause) {
            throw new Fault({ cause });
        }

        await this.reply(`Banned ${target.username}.`);
    }
}
```

`cause` is the error you caught. It reaches your logs and your fault report. The user never sees it.

{/* prettier-ignore-start */}

| you write                      | reported                     |
| ------------------------------ | ---------------------------- |
| `new Fault()`                  | yes                          |
| `new Fault({ cause })`         | yes, with that error's stack |
| `new Fault({ report: false })` | no                           |

{/* prettier-ignore-end */}

All three render the same card.

Letting the error escape shows that card too. The difference is which report it reaches. A `Fault` goes to `handledException`, beside the refusals you chose to report. An error nobody caught goes to `unknownException`. Catch and wrap the calls you expect to fail, so `unknownException` only shows you the bugs you haven't found yet.

## The card every fault renders

<Image src="/fault-card.webp" alt="The fault card in Discord, reading Error, Something went wrong, above a copyable UUID" width="1334" height="350" />

[Replace the card](/replying/error-behavior#replacing-the-card) when you want a different look or an action on it.

seedcord logs that uuid and puts it on the fault report, so a user who pastes it into your support channel gives you the exact log line to search for.

`the developer` is the default contact. Set `notifications.developerUsername` to put your own name there.

```ts title="src/bot.ts"
export const seedcord = new Seedcord({
    bot,
    subscribers: { path: null },
    notifications: {
        developerUsername: 'materwelon'
    }
});
```

## Anything else you throw

Any throw that is neither a [`Notice`](https://docs.seedcord.org/packages/core/latest/notice) nor a [`Silence`](https://docs.seedcord.org/packages/core/latest/silence) renders that same card. That includes a `TypeError` from reading a property of `undefined`, a `DiscordAPIError` from a call you made yourself, and an error thrown inside a library you installed. seedcord logs each one with its uuid and publishes it to the `unknownException` bus key. Your error's message never reaches the reply.

Two config keys change this. `errors.defaultError` takes a `Notice` subclass that seedcord constructs with the uuid, and `errors.ignoreApiCodes` takes a list of Discord api codes to swallow. [Configuring](/replying/error-behavior) has both in a real config.

## Stopping without a reply

`Silence` stops the handler, and seedcord does not call Discord at all. That fits an event handler, since nothing is waiting on a response.

```ts title="src/events/Welcome.ts"
@RegisterEvent([Events.GuildMemberAdd])
export class Welcome extends EventHandler<Events.GuildMemberAdd> {
    public async execute(): Promise<void> {
        const [member] = this.event;
        const template = await this.template(member.guild.id);

        await member.guild.systemChannel?.send(
            template.replace('{user}', `${member}`)
        );
    }

    private async template(guildId: string): Promise<string> {
        const saved = await settings.welcome(guildId);
        if (!saved) throw new Silence('no welcome message set');

        return saved;
    }
}
```

`template()` throws from inside a helper, and `execute()` never checks a return value, the same way a `Notice` works in an interaction handler.

> **Gateway only**
>
> Only a gateway bot receives client events, so only `@seedcord/gateway` exports [`EventHandler`](https://docs.seedcord.org/packages/gateway/latest/event-handler). In an event handler, a `Notice` with `report` false also stops without a report.

The reason string reaches a debug log and stops there. `new Silence()` without one skips that log line, and setting `errors.logSilences` to `false` skips it for every `Silence`.

> **Danger**
>
> Throwing a `Silence` from an interaction handler leaves the user with Discord's own failure message. Before any ack Discord says the application did not respond, and after a `defer()` the thinking placeholder stays. Throw a `Notice` there.

## Throws outside a handler

seedcord registers an `unhandledRejection` and an `uncaughtException` listener while your bot starts. They catch the throws that reach Node itself, like a promise you forgot to await or a `setTimeout` callback that threw. Each one reports as an unknown fault.

{/* prettier-ignore-start */}

| listener             | route id on the report       | what happens next                            |
| -------------------- | ---------------------------- | -------------------------------------------- |
| `unhandledRejection` | `process:unhandledRejection` | the bot keeps running                        |
| `uncaughtException`  | `process:uncaughtException`  | seedcord runs the shutdown tasks and exits 1 |

{/* prettier-ignore-end */}

Both fire outside any interaction, so nobody sees a reply. When your application registers its own pair, set `errors.catchProcessErrors` to `false`, which [Configuring](/replying/error-behavior) shows in a real config.

> **Http only**
>
> An edge bot registers neither listener, since [`createSeedcord`](https://docs.seedcord.org/packages/http/latest/create-seedcord) returns a request handler and never starts a host process.

Every reported throw ends up in your logs and on the bus. [Reporting faults](/replying/reporting) sends them to a Discord channel you read.
