{"record":{"id":"683cf976ba274978","repo":"can1357/oh-my-pi","slug":"discord-returned-an-invalid-message-response","errorCode":null,"errorMessage":"Discord returned an invalid message response","messagePattern":"Discord returned an invalid message response","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-discord.ts","lineNumber":54,"sourceCode":"\t\tthrow new Error(\"Discord webhook credential must use HTTPS\");\n\t}\n\tconst segments = url.pathname.split(\"/\").filter(Boolean);\n\tconst webhooksIndex = segments.indexOf(\"webhooks\");\n\tconst id = webhooksIndex >= 0 ? segments[webhooksIndex + 1] : undefined;\n\tconst token = webhooksIndex >= 0 ? segments[webhooksIndex + 2] : undefined;\n\tif (!id || !token || !/^\\d+$/.test(id)) {\n\t\tthrow new Error(\"Discord webhook credential does not contain a webhook ID and token\");\n\t}\n\treturn { id, token };\n}\n\nfunction webhookEndpoint(webhook: DiscordWebhook, suffix?: string): URL {\n\tconst base = `${DISCORD_API_ORIGIN}/api/v10/webhooks/${encodeURIComponent(webhook.id)}/${encodeURIComponent(webhook.token)}`;\n\treturn new URL(suffix ? `${base}/${suffix}` : base);\n}\n\nfunction parseMessage(value: unknown): DiscordMessage {\n\tif (!value || typeof value !== \"object\") throw new Error(\"Discord returned an invalid message response\");\n\tconst message = value as Record<string, unknown>;\n\tif (typeof message.id !== \"string\") throw new Error(\"Discord response did not include a message ID\");\n\tif (!Array.isArray(message.attachments)) throw new Error(\"Discord response did not include an attachment\");\n\tconst first = message.attachments[0];\n\tif (!first || typeof first !== \"object\") throw new Error(\"Discord response did not include an attachment\");\n\tconst attachmentUrl = (first as Record<string, unknown>).url;\n\tif (typeof attachmentUrl !== \"string\") throw new Error(\"Discord attachment did not include a URL\");\n\ttry {\n\t\tconst parsed = new URL(attachmentUrl);\n\t\tif (parsed.protocol !== \"https:\") throw new Error();\n\t} catch {\n\t\tthrow new Error(\"Discord attachment URL is invalid\");\n\t}\n\treturn { id: message.id, attachmentUrl };\n}\n\nfunction attachmentExpiry(url: string, now: number): number {\n\tconst signedExpiry = new URL(url).searchParams.get(\"ex\");","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-discord.ts#L36-L72","documentation":"After uploading the file via the Discord webhook (with ?wait=true so Discord returns the created message), the uploader validates the response JSON: it must be an object containing a message `id` string and a non-empty `attachments` array. A body that is not an object fails this first check, meaning Discord did not return the expected message resource.","triggerScenarios":"POST to /api/v10/webhooks/<id>/<token>?wait=true returned 2xx but the body is not a JSON object — an HTML error/interstitial page from a proxy, a JSON array, `null`, or an empty body being decoded into a non-object value.","commonSituations":"Corporate proxies or Cloudflare challenge pages intercepting discord.com; a stubbed Discord endpoint in tests returning an incomplete body; Discord API changes; hitting a rate-limit/gateway response that still parsed to a non-object.","solutions":["Check the raw response body and status from the webhook POST to see what Discord actually returned","Ensure no proxy or DNS override (e.g. for censored regions) is injecting HTML challenge pages for discord.com","Retry the upload — transient gateway errors can return non-JSON bodies","Verify webhook validity via GET /api/v10/webhooks/<id>/<token> and that `wait=true` is honored for your webhook type"],"exampleFix":"// before (mock returns a bare JSON array)\n[{ \"id\": \"1\" }]\n// after (return the message object Discord sends)\n{ \"id\": \"9999\", \"attachments\": [{ \"id\": 0, \"url\": \"https://cdn.discordapp.com/attachments/.../file.txt\" }] }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isDiscordMessage(v) {\n  return typeof v === 'object' && v !== null &&\n    typeof v.id === 'string' &&\n    Array.isArray(v.attachments) && v.attachments.length > 0 &&\n    typeof v.attachments[0]?.url === 'string';\n}","tryCatchPattern":"try {\n  const body = await response.json();\n  if (!isDiscordMessage(body)) {\n    throw new Error(`Discord webhook returned unexpected body: ${JSON.stringify(body).slice(0, 200)}`);\n  }\n} catch (err) {\n  logger.error('Discord webhook upload failed or response malformed', { cause: err });\n  // retry once or fall back to another destination\n}","preventionTips":["Keep ?wait=true so Discord returns the full message object","Retry uploads on transient gateway/proxy failures","Log non-JSON or malformed bodies to detect proxies injecting challenge pages","Verify the webhook is valid (GET the webhook endpoint) before relying on it in production"],"tags":["discord","webhook","api-response","network"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}