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
| Code | Meaning |
|---|---|
| 10003 | Unknown channel |
| 10008 | Unknown message |
| 10062 | Unknown interaction (you responded too late, the 3 second window) |
| 40060 | Interaction already acknowledged |
| 50001 | Missing access |
| 50013 | Missing permissions |
| 50035 | Invalid 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.