Errors

Athena has three error families plus the CommandError discriminator used by the commands framework, and one endpoint-specific error (SearchIndexNotReadyError).

Athena has three error families plus the CommandError discriminator used by the commands framework, and one endpoint-specific error (SearchIndexNotReadyError).

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.

SearchIndexNotReadyError

Thrown by client.searchGuildMessages when Discord is still building the guild's search index. Retry the search after the delay.

import { SearchIndexNotReadyError } from 'athena';
 
try {
  return await client.searchGuildMessages(guildID, { content: 'deploy' });
} catch (err) {
  if (err instanceof SearchIndexNotReadyError) {
    // err.retryAfter: seconds to wait before retrying (0 means retry after a short delay)
    // err.documentsIndexed: documents indexed so far by the current indexing operation
    setTimeout(retrySearch, Math.max(err.retryAfter, 1) * 1000);
  }
}

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.