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

  1. 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.
  2. Validate the embed before sending: non-empty title/description, ≤25 fields, ≤6000 total characters.
  3. Add retry with exponential backoff for 429/5xx responses before surfacing the failure.
  4. 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

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)