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 takes an errors block on its config. Every key in it is optional.
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]
}
});| 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 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 |
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 and build your own card.
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.
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 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, every code the framework throwsisSeedcordError, the guard belowpaint, 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.
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.