affaan-m/ECC · error · Error
Discord request failed
Error message
Discord request failed (${response.status}) What it means
discord() is the low-level wrapper for all Discord REST v10 calls and throws this whenever `response.ok` is false, embedding the status code. Non-2xx covers 400 (bad body), 401 (bad bot token), 403 (missing permissions), 404 (unknown channel/thread), and 429 (rate limited).
Solutions
- Read the status: 401 -> regenerate/fix DISCORD_BOT_TOKEN; 403 -> invite bot with Send Messages permission; 404 -> fix channel ID; 429 -> honor Retry-After with backoff; 400 -> validate the message payload
- Log the response body, which contains Discord's specific error codes and messages
- Verify the bot is a member of the target guild and can access the channel
- Add retry-with-backoff handling for 429 before surfacing the error
Example fix
// before
if (!response.ok) throw new Error(`Discord request failed (${response.status})`);
// after
if (!response.ok) {
const detail = await response.text();
if (response.status === 429) {
const retryAfter = Number(response.headers.get('retry-after') || 1) * 1000;
await new Promise(r => setTimeout(r, retryAfter));
// retry once, then throw
}
throw new Error(`Discord request failed (${response.status}): ${detail}`);
} Defensive patterns
Strategy: retry
Validate before calling
// validate token and channel before sending
const tokenRes = await fetch('https://discord.com/api/v10/users/@me', { headers: { Authorization: `Bot ${process.env.DISCORD_BOT_TOKEN}` } });
if (tokenRes.status === 401) throw new Error('DISCORD_BOT_TOKEN is invalid or expired');
const chanRes = await fetch(`https://discord.com/api/v10/channels/${process.env.DISCORD_CHANNEL_ID}`, { headers: { Authorization: `Bot ${process.env.DISCORD_BOT_TOKEN}` } });
if (!chanRes.ok) throw new Error(`Channel not accessible: ${chanRes.status}`); Try / catch
try {
await discord('POST', `/channels/${channelId}/messages`, { content });
} catch (err) {
const m = /Discord request failed \((\d+)\)/.exec(err.message);
if (m && m[1] === '429') {
await sleep(retryAfterMs); // retry with backoff
} else if (m && ['401','403','404'].includes(m[1])) {
console.error('Fix bot token, permissions, or channel id — no retry will help');
}
throw err;
} Prevention
- Preflight the bot token with GET /users/@me before runs
- Invite the bot with Send Messages / Send Messages in Threads permissions on the target channel
- Add automatic retry with Retry-After handling for 429 responses
- Log Discord's JSON error body (it includes numeric error codes explaining the failure)
- For forum channels, create a thread first instead of posting messages directly
When it happens
Trigger: Any discord(method, path, body) call — e.g. POST to /channels/{id}/messages or threads endpoint — returning non-ok: 401 invalid DISCORD_BOT_TOKEN, 403 bot lacks Send Messages / permissions in the channel, 404 wrong channel ID, 429 rate limited, 400 malformed message body (empty content, invalid embed).
Common situations: Expired or reset bot token; bot not added to the server/channel; missing Message Content or Send Messages intents/permissions; announcement posted to a forum channel that only accepts threads; hitting Discord's 5 req/5s channel rate limit on bulk runs.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Control-plane request failed
- Discord webhook request failed
- discussion lookup failed
- download declares an invalid or excessive size
- download declares an invalid or excessive size
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/cb5e2d0a0c56f8a9.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/discord/release-announce.mjs:143
{ id: discussionId, body },
);
return data.addDiscussionComment.comment.id;
}
async function deleteReceiptComment(commentId) {
await githubGraphql(
`mutation($id:ID!){deleteDiscussionComment(input:{id:$id}){clientMutationId}}`,
{ id: commentId },
);
}
async function discord(method, path, body) {
const response = await request(`https://discord.com/api/v10${path}`, {
method,
headers: { Authorization: `Bot ${env.DISCORD_BOT_TOKEN}`, 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined,
});
if (!response.ok) throw new Error(`Discord request failed (${response.status})`);
return response.status === 204 ? null : response.json();
}
async function deliver(discussion) {
const key = announcementKey({ repository: env.GITHUB_REPOSITORY, discussionId: discussion.id });
if (env.DISCORD_ANNOUNCE_WEBHOOK_URL) {
if (!env.GITHUB_TOKEN) throw new Error('GitHub receipt configuration is missing');
const webhookUrl = normalizeDiscordWebhookUrl(env.DISCORD_ANNOUNCE_WEBHOOK_URL);
const marker = discussionReceiptMarker(key);
const existingReceipt = await findReceiptComment(discussion.id, marker);
if (existingReceipt) {
if (discussionReceiptStatus(existingReceipt) === 'complete') {
console.log('announcement already delivered');
return;
}
throw new Error('announcement has a pending receipt; inspect Discord before clearing it');
}
const claimId = await addReceiptComment(discussion.id, `${marker}\n\nDiscord delivery: pending.`);View on GitHub (pinned to 8321021c54)