Errors

Athena has three error families plus the CommandError discriminator used by the commands framework.

Athena has three error families plus the CommandError discriminator used by the commands framework.

DiscordRESTError

Thrown when Discord returns a structured JSON error (4xx with a code and message). Carries Discord's numeric code.

import { DiscordRESTError } from 'athena';
 
try {
  await client.createMessage(channelID, '...');
} catch (err) {
  if (err instanceof DiscordRESTError) {
    console.error(err.code, err.message); // e.g. 50001 Missing Access
  }
}

Properties: code (Discord error code), message (flattened, including field-level errors), response (parsed body), req, res, headers, name (DiscordRESTError [code]).

DiscordHTTPError

Thrown for transport-level or non-JSON failures (a 502 after retries, a CloudFlare HTML page). err.code is the HTTP status.

CommandError

The commands framework does not throw on command failures; it emits a commandError event with a discriminated payload.

import { CommandError } from 'athena';
 
client.on('commandError', (command, interaction, error) => {
  switch (error.type) {
    case CommandError.NotDeveloperGuild: break;           // error.data: guild ID
    case CommandError.DeniedUser: break;                  // error.data: user ID
    case CommandError.DeniedGuild: break;                 // error.data: guild ID
    case CommandError.MissingPermission: break;           // error.data: { userID, permissions }
    case CommandError.OnCooldown: break;                  // error.data: { userID, cooldown }
    case CommandError.MiddlewareError: break;             // error.data: the thrown value
    case CommandError.UncaughtError: break;               // error.data: Error; error.interaction: which handler
  }
});

A reusable reporter:

client.on('commandError', async (command, interaction, error) => {
  try {
    if (error.type === CommandError.OnCooldown) {
      const secs = Math.max(0, Math.ceil(((error.data.cooldown.expiry ?? 0) - Date.now()) / 1000));
      return await interaction.createMessage({ content: `On cooldown for ${secs}s.`, flags: MessageFlags.Ephemeral });
    }
    if (error.type === CommandError.MissingPermission) {
      return await interaction.createMessage({ content: `You need: ${error.data.permissions.join(', ')}`, flags: MessageFlags.Ephemeral });
    }
    if (error.type === CommandError.UncaughtError) {
      logger.error({ command, err: error.data });
      return await interaction.createMessage({ content: 'Something went wrong.', flags: MessageFlags.Ephemeral });
    }
  } catch {
    // the interaction may already be answered; ignore follow-up failures
  }
});

Common Discord codes

CodeMeaning
10003Unknown channel
10008Unknown message
10062Unknown interaction (you responded too late, the 3 second window)
40060Interaction already acknowledged
50001Missing access
50013Missing permissions
50035Invalid form body

Full list: https://discord.com/developers/docs/topics/opcodes-and-status-codes#json.

Tip

10062 and 40060 almost always mean a timing or double-ack bug: respond once, within three seconds, and defer() first if the work takes longer.