thedotmack/claude-mem · error · Error
Discord API error
Error message
Discord API error: ${res.status} - ${errorText.slice(0, 200)} What it means
`postDiscordEmbed` throws this when the Discord webhook/API responds with a non-OK status to a POST containing an embed. The message carries the HTTP status and the first 200 characters of Discord's error body, which normally explains the rejection (invalid webhook URL, bad embed schema, or rate limit). Callers are `sendProbeEmbed` (canary notifications) and `runWatchdog` (alerting), so a throw means alert delivery failed.
Solutions
- Check the embedded status: 404/401 → regenerate the webhook URL and update the secret; 429 → respect `retry_after` and back off; 400 → fix the embed payload.
- Validate the embed before sending: non-empty title/description, ≤25 fields, ≤6000 total characters.
- Add retry with exponential backoff for 429/5xx responses before surfacing the failure.
- Verify the webhook secret is set in the worker environment (not a stale or deleted webhook).
Example fix
// before
if (!res.ok) {
const errorText = await res.text().catch(() => "");
throw new Error(`Discord API error: ${res.status} - ${errorText.slice(0, 200)}`);
}
// after
if (res.status === 429) {
const { retry_after = 1 } = await res.json().catch(() => ({} as any));
await sleep(retry_after * 1000);
return postDiscordEmbed(url, embed); // bounded retry
}
if (!res.ok) {
const errorText = await res.text().catch(() => "");
throw new Error(`Discord API error: ${res.status} - ${errorText.slice(0, 200)}`);
} Defensive patterns
Strategy: retry
Validate before calling
// validate the embed and webhook URL before sending
function assertEmbedValid(embed: { title?: string; description?: string; fields?: unknown[] }): void {
if (!embed.title && !embed.description) throw new Error('embed needs title or description');
if ((embed.fields?.length ?? 0) > 25) throw new Error('embed exceeds 25 fields');
}
function assertWebhookUrl(url: string): void {
if (!/^https:\/\/discord\.com\/api\/webhooks\/\d+\/[\w-]+$/.test(url)) throw new Error('invalid webhook url');
} Type guard
function isDiscordErrorBody(body: unknown): body is { message: string; retry_after?: number } {
const b = body as any;
return b && typeof b.message === 'string';
} Try / catch
try {
await postDiscordEmbed(webhookUrl, embed);
} catch (err) {
if (err instanceof Error && err.message.startsWith('Discord API error: 429')) {
await sleep(extractRetryAfter(err.message) ?? 1000);
await postDiscordEmbed(webhookUrl, embed); // bounded retries
} else if (err instanceof Error && err.message.startsWith('Discord API error: 4')) {
console.error('webhook misconfigured, alerting will fail:', err.message); // do not retry 4xx
} else throw err;
} Prevention
- Store the webhook URL in a secret and verify it after any Discord channel reorganization.
- Throttle watchdog notifications to avoid 429s; batch alerts into one embed.
- Validate embed size/field limits before every send.
- Alert on alert-delivery failure via a secondary channel so watchdog outages are visible.
When it happens
Trigger: Any non-ok response from the Discord webhook fetch: 404 when the webhook was deleted or the URL is wrong, 401 for an invalid webhook token, 400 when the embed violates Discord's schema (empty name, field count/title limits), or 429 when the rate limit is exhausted.
Common situations: Discord webhook URL rotated or the channel was deleted after a config change; watchdog sending too many alerts in a short window and hitting 429; embed built with an empty/oversized description or >25 fields; secret not set so the URL is a placeholder.
Related errors
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/5ebd8d870e6984fa.
Report an issue: GitHub.
Appendix: source
Thrown at workers/sync-hub/src/discord.ts:41
color: number;
fields: DiscordEmbedField[];
footer: { text: string };
timestamp: string;
}
export async function postDiscordEmbed(
webhookUrl: string,
embed: DiscordEmbed,
fetchImpl: typeof fetch = globalThis.fetch,
): Promise<void> {
const res = await fetchImpl(webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ embeds: [embed] }),
});
if (!res.ok) {
const errorText = await res.text().catch(() => "");
throw new Error(`Discord API error: ${res.status} - ${errorText.slice(0, 200)}`);
}
}
View on GitHub (pinned to d8bc9755e7)