Skip to content

The framework's bus keys

Subscribe to the keys seedcord publishes on the bus. Covers which transport publishes each key, deploys, incoming interactions and events, unhandled errors, and faults.

seedcord publishes its own keys on core.bus, alongside the keys you declare yourself. Without them, logging every deploy, tracing each interaction, or posting faults to a channel means wrapping your own handlers and deploy script. You subscribe to them with @Subscribe or core.bus.on, the same way you subscribe to your own keys. You can't publish them yourself.

The where column says which transport publishes each key.

keywherefires when
unknownExceptionbotha raw throw reaches the fault boundary, or the Node process catches an unhandled error
handledExceptionbotha Notice with report true is caught
unhandledInteractionErrorbothan interaction dispatch throws past the boundary
interactionDispatchedbothone dispatch finishes
responseAttemptedbothany write through the reply surface
commandsDeployedbotha command deploy finishes
anyInteractionbothan interaction arrives, before routing
unhandledEventErrorgatewayan event dispatch throws past the boundary
eventDispatchinggatewaybefore the handlers for an event run
eventDispatchedgatewaythose handlers finish

Gateway and http differ

anyInteraction carries a discord.js Interaction on gateway and an APIInteraction on http. If a subscriber calls a discord.js method on it, that subscriber compiles on gateway and fails to compile on http.

On http, seedcord publishes it only after the request passes Ed25519 verification and the replay checks. A forged or repeated request never reaches it.

After a deploy

seedcord logs every deploy and fills Commands with each command's mention, so your handlers don't need this key. commandsDeployed is for sending your commands somewhere outside the bot, like a bot listing site such as Top.gg that shows your command list. It fires after every deploy, including the redeploy that follows a hot reload.

src/subscribers/ListCommands.ts
import { Subscribe, Subscriber } from '@seedcord/gateway';

@Subscribe('commandsDeployed')
export class ListCommands extends Subscriber<'commandsDeployed'> {
    public async execute(): Promise<void> {
        // topgg is a client from Top.gg's own SDK
        await topgg.postCommands(this.data.global);
    }
}

global and the values in guilds are APIApplicationCommand[] straight from Discord, so you can pass them on as they are. A guild command only exists in its own guild, which is why ListCommands sends global alone.

Watching everything come in

A trace of everything your bot receives helps when a command doesn't seem to run at all. anyInteraction fires before seedcord routes an interaction anywhere. eventDispatching fires for client events, ahead of the handlers.

src/subscribers/Trace.ts
@Subscribe('eventDispatching')
export class Trace extends Subscriber<'eventDispatching'> {
    public async execute(): Promise<void> {
        const { name, args } = this.data;

        this.logger.debug(
            `${name} arrived with ${args.length} args`
        );
    }
}

Trace reads only args.length, which works for any event. name and args narrow together, so a check like name === 'messageCreate' gives you that event's own tuple in args.

Warning

eventDispatching fires only when at least one handler is about to run. It doesn't fire for an event that no handler registered, or for an event whose handlers were all once and have already run. It fires before your middleware runs, so if a middleware stops the event, this key has already been published.

eventDispatched fires once the handlers finish. Its fields, including each handler's outcome, are on the telemetry page.

The two unhandled errors

A throw from your handlers, gates, or middleware reaches the fault boundary, which catches and reports it. unhandledInteractionError and unhandledEventError cover a throw the boundary never sees, like one inside seedcord's own dispatch code (report it (opens in a new tab)). Both carry only { error: Error }. Subscribe to them to hear about those failures in production, since your handlers' own reporting never sees them.

The fault keys

These two keys carry a fault out of your bot, to a log service or a Discord channel. unknownException fires on a raw throw.

fieldtypewhat it holds
uuidUUIDthis fault, and the id the error card shows the user
dispatchIdstring | nullthe dispatch it came from, null on a process-level throw such as an unhandled rejection
errorErrorthe throw itself
originstringwhere it came from, for example slash:ban or event:messageCreate:AutoMod
guild{ id, name }optional, the guild the dispatch ran in
user{ id, username }optional, whoever ran it
metadataunknownoptional. On an event fault it holds the event name, the handler, and the args

A reported Notice thrown from an autocomplete also publishes here, since FaultSource only has shapes for interactions and events. Check error with instanceof Notice if your subscriber treats the two differently.

handledException fires on a Notice with report true.

fieldtypewhat it holds
uuidUUIDthis fault, and the id the error card shows the user
dispatchIdstringthe dispatch that reported it
originstringthe same shape unknownException carries
denialNoticethe Notice itself
sourceFaultSourcewhere the throw came from, with kind of interaction or event (http only produces interaction)

An event origin carries a third segment naming the handler, since one dispatch runs several of them.

seedcord ships a subscriber for each of these keys and registers it for you. Each one posts a card to a Discord webhook once you set its environment variable.

.env
UNKNOWN_EXCEPTION_WEBHOOK_URL=https://discord.com/api/webhooks/...
HANDLED_EXCEPTION_WEBHOOK_URL=https://discord.com/api/webhooks/...

If a variable isn't set, that reporter stays off. seedcord logs a warning about it at startup.