affaan-m/ECC · error · Error

Discord webhook request failed

Error message

Discord webhook request failed (${response.status})

What it means

After POSTing the announcement payload to the Discord webhook, the script checks response.ok. Any non-2xx status causes it to delete the pending receipt claim (to allow retries) and throw this error containing the HTTP status code.

Solutions

  1. Check the status code in the message: 404/401 means regenerate the webhook URL in Discord channel settings and update DISCORD_ANNOUNCE_WEBHOOK_URL
  2. 400: shorten the release body/title so the payload fits Discord limits
  3. 429: retry after waiting; Discord rate limits webhooks (~5 req/2s)
  4. Re-run the workflow — the pending receipt was deleted so the run is retryable
Defensive patterns

Strategy: retry

Validate before calling

const res = await fetch(webhookUrl, { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(payload) }); if (!res.ok) console.error(`Webhook returned ${res.status}`);

Type guard

const isRetryable = (status) => status === 429 || status >= 500;

Try / catch

try { await deliver(discussion); } catch (e) { const m = /failed \((\d+)\)/.exec(e.message); if (m && (m[1] === '429' || +m[1] >= 500)) { await sleep(backoff); return deliver(discussion); } throw e; }

Prevention

When it happens

Trigger: request(webhookUrl, { method: 'POST', ... }) returns a non-ok response — e.g. 404 (webhook deleted/revoked), 400 (malformed payload), 429 (rate limited), or 401/403.

Common situations: Webhook URL revoked from Discord channel settings; message content exceeding Discord limits (embed/body too long); posting faster than Discord's per-webhook rate limit allows.

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/49730997871f63e0. Report an issue: GitHub.

Appendix: source

Thrown at scripts/discord/release-announce.mjs:172

    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.`);
    const payload = buildDiscordPayload({ title: discussion.title, body: discussion.body, url: discussion.url, key });
    delete payload.nonce;
    delete payload.enforce_nonce;
    const response = await request(webhookUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });
    if (!response.ok) {
      await deleteReceiptComment(claimId);
      throw new Error(`Discord webhook request failed (${response.status})`);
    }
    const message = await response.json();
    await addReceiptComment(discussion.id, `${marker}\n\nDiscord delivery: complete (message ${message.id}).`);
    await deleteReceiptComment(claimId).catch(() => {
      console.warn('announcement delivered; pending receipt cleanup requires attention');
    });
    console.log('announcement delivered by channel webhook');
    return;
  }
  if (!env.DISCORD_BOT_TOKEN || !/^\d{10,25}$/.test(env.DISCORD_ANNOUNCE_CHANNEL_ID || '')) {
    throw new Error('Discord announcement credentials are missing or invalid');
  }
  const recent = await discord('GET', `/channels/${env.DISCORD_ANNOUNCE_CHANNEL_ID}/messages?limit=100`);
  const receipt = findDiscordReceipt(recent, key);
  if (receipt) {
    await discord('PUT', `/channels/${env.DISCORD_ANNOUNCE_CHANNEL_ID}/pins/${receipt.id}`);
    console.log('announcement already delivered; pin verified');
    return;

View on GitHub (pinned to 8321021c54)