instanceof tells you what went wrong without parsing a message. For the practical pattern, see Handling errors.
A denied
send() is none of these. It arrives on the sendRejected event, because send() is fire-and-forget.CloakActionError
The typed rejection for an awaited action the server refused.string
A human-readable explanation, for example
missing the kick-members permission or ban reason too long (max 256 chars). This is the stable field to show or log.PermissionName | undefined
The named permission the bot was denied on, when the failure maps to one. Deleting another member’s message without
message_manage sets this to 'message_manage'. It is undefined when the failure does not map to a named permission, so always fall back to message.number
A raw numeric diagnostic identifying the action. For logging only.
number
A raw numeric diagnostic for the specific failure. For logging only.
Which calls reject with it
Every awaited action the server can refuse:- Message actions:
react,unreact,edit,delete,pin,unpin. - Moderation:
kick,ban,unban,timeout,removeTimeout,fetchBans. - Channels and groups:
createChannel,editChannel,deleteChannel,moveChannel,createGroup,editGroup,deleteGroup. - Reads:
fetchRoles,fetchEmojis, andfetchMessageswhen read history is denied. - Direct messages:
sendDM, on the create and key-exchange legs. An ineligible target is refused here. - Presence:
setStatus. - Voice:
joinVoiceandleaveVoice. - Commands:
sendCommand. - Webhook CRUD:
webhooks.list,.create,.edit,.regenerate,.delete. - REST:
client.rest.*, when the credential mint is refused.
send(), sendDM() after the DM exists, msg.reply(), msg.channel.send(), sendTyping(), and setAvatar() are fire-and-forget for delivery. Their promises resolve when the frame goes out. See Denied sends.When there is no named permission
Some denials are permission-shaped but do not map to one of the 31 named permissions. Pinning is the clearest example: it is gated by a channel-level pin permission that has no name in the vocabulary.permission is undefined there, and message still explains what happened.
Denied sends
send() has no success acknowledgement on the wire, so its promise resolves once the frame is out. A server-side denial arrives afterwards, on the sendRejected event.
{ code, message, permission?, serverId, channelId, frame }, with the same message and permission semantics as CloakActionError.
sendRejected does not also fire error. A backend denial is an expected outcome, not a plumbing failure.
CloakClientError
Something failed inside the SDK’s own plumbing: one of your handlers threw, a key pull died, or an inbound frame could not be dispatched. It surfaces on theerror event rather than as a rejection, because there is no call for it to reject.
CloakErrorSource
Where it came from:
'listener', 'frame', 'keys', 'transport', or 'reconnect'.string | undefined
The event name whose listener threw when
source is 'listener'. Otherwise a label for the failing call site.unknown
The original failure, never swallowed and never wrapped twice.
error listener, the behavior is what it was before the event existed: silent, plus a warning under debug.
CloakEnvironmentError
The host is wrong, not your bot. An unsupported Node runtime, an optional native module the image cannot load, two packages that must move in lockstep and did not, an executable the audio path expected onPATH.
string
One actionable paragraph naming what to change on the machine. Nothing about your bot’s code, token, or permissions is at fault, so print this next to
message.login() raises one on the first connect for a failure it recognizes, including an unsupported Node version. Reconnects keep their normal backoff and log the remedy under debug. The voice audio path raises the same class from player.play() and ytdlpResource(). See Troubleshooting.
CloakRestError
An HTTP leg of the REST lane failed: cloak-rest-api answered non-2xx, or the request never left the host.number
The HTTP status, or
0 for a transport failure such as DNS, TLS, or a timeout.string
The request path with the query string stripped, because ids ride query strings.
string | undefined
The service’s machine-readable error code when the body carried one, for example
file_too_large. Surfaced verbatim: the SDK hardcodes no server-side limit.message, in a field, or in toString().
The credential mint is not an HTTP failure and rejects with
CloakActionError instead. The most likely first error anyone sees from client.rest is code -7: bot REST credentials are disabled server-side, which is the shipping default. Nothing is wrong with your bot. An operator has to enable the feature.CloakEmbedError
A cardsendCard() refused, thrown locally before anything reaches the wire. This class exists because the server cannot read an encrypted card and the renderer strips bad values silently, so this is the only place you can be told.
CloakEmbedErrorReason
One of
'shape', 'content', 'cap', 'media-host', 'empty', or 'wire-cap'.string | undefined
The exact offending path, for example
'embed.image.url'. Absent when the whole payload is at fault.'media-host' is the one you are most likely to hit: card media must be Cloak-hosted, and there is deliberately no flag that widens it. See Rich cards.
CloakWebhookError
A webhook POST failed, from eitherwebhooks.post() or sendEmbed().
number
The HTTP status, or
0 for a transport failure or a local precheck.string
The ingress’ own label, such as
'unauthorized', 'rate limited', or 'embeds too large'. When status is 0, an SDK-side label such as 'transport' or 'precheck'.string | undefined
The webhook this post targeted, when known. Never the secret.
Login failures
Login rejections do not throw from a call you can await once the reconnect loop owns the connection. They emitdisconnect.
Two are terminal and stop the loop, because retrying cannot fix either:
-8 is a transient backend error and keeps retrying with backoff. If your bot goes quiet and no reconnecting follows a disconnect, you are looking at -2 or -12. See Connection lifecycle.
describeActionError
(opcode, code) pair to the same message and optional permission that CloakActionError uses. Unknown codes get a generic message. You rarely need it, since a thrown error already carries both. It is there for tooling that inspects raw failures.
Next
Handling errors
The catch-and-report pattern in practice.
Permissions
What the permission field points at.
Events
The error and sendRejected events.
Troubleshooting
Environment failures and their remedies.