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

  1. 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
  2. Log the response body, which contains Discord's specific error codes and messages
  3. Verify the bot is a member of the target guild and can access the channel
  4. 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

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


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)