{"record":{"id":"5ebd8d870e6984fa","repo":"thedotmack/claude-mem","slug":"discord-api-error-res-status-errortext-slice-0-200","errorCode":null,"errorMessage":"Discord API error: ${res.status} - ${errorText.slice(0, 200)}","messagePattern":"Discord API error: (.+?) - (.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"workers/sync-hub/src/discord.ts","lineNumber":41,"sourceCode":"\tcolor: number;\n\tfields: DiscordEmbedField[];\n\tfooter: { text: string };\n\ttimestamp: string;\n}\n\nexport async function postDiscordEmbed(\n\twebhookUrl: string,\n\tembed: DiscordEmbed,\n\tfetchImpl: typeof fetch = globalThis.fetch,\n): Promise<void> {\n\tconst res = await fetchImpl(webhookUrl, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\tbody: JSON.stringify({ embeds: [embed] }),\n\t});\n\tif (!res.ok) {\n\t\tconst errorText = await res.text().catch(() => \"\");\n\t\tthrow new Error(`Discord API error: ${res.status} - ${errorText.slice(0, 200)}`);\n\t}\n}\n","sourceCodeStart":23,"sourceCodeEnd":44,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/workers/sync-hub/src/discord.ts#L23-L44","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\nif (!res.ok) {\n  const errorText = await res.text().catch(() => \"\");\n  throw new Error(`Discord API error: ${res.status} - ${errorText.slice(0, 200)}`);\n}\n\n// after\nif (res.status === 429) {\n  const { retry_after = 1 } = await res.json().catch(() => ({} as any));\n  await sleep(retry_after * 1000);\n  return postDiscordEmbed(url, embed); // bounded retry\n}\nif (!res.ok) {\n  const errorText = await res.text().catch(() => \"\");\n  throw new Error(`Discord API error: ${res.status} - ${errorText.slice(0, 200)}`);\n}","handlingStrategy":"retry","validationCode":"// validate the embed and webhook URL before sending\nfunction assertEmbedValid(embed: { title?: string; description?: string; fields?: unknown[] }): void {\n  if (!embed.title && !embed.description) throw new Error('embed needs title or description');\n  if ((embed.fields?.length ?? 0) > 25) throw new Error('embed exceeds 25 fields');\n}\nfunction assertWebhookUrl(url: string): void {\n  if (!/^https:\\/\\/discord\\.com\\/api\\/webhooks\\/\\d+\\/[\\w-]+$/.test(url)) throw new Error('invalid webhook url');\n}","typeGuard":"function isDiscordErrorBody(body: unknown): body is { message: string; retry_after?: number } {\n  const b = body as any;\n  return b && typeof b.message === 'string';\n}","tryCatchPattern":"try {\n  await postDiscordEmbed(webhookUrl, embed);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Discord API error: 429')) {\n    await sleep(extractRetryAfter(err.message) ?? 1000);\n    await postDiscordEmbed(webhookUrl, embed); // bounded retries\n  } else if (err instanceof Error && err.message.startsWith('Discord API error: 4')) {\n    console.error('webhook misconfigured, alerting will fail:', err.message); // do not retry 4xx\n  } else throw err;\n}","preventionTips":["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."],"tags":["discord","http","webhook","error-response"],"backgroundTag":"api-error-response","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}