{"record":{"id":"9fc056880c40be66","repo":"can1357/oh-my-pi","slug":"discord-webhook-credential-does-not-contain-a-webh","errorCode":null,"errorMessage":"Discord webhook credential does not contain a webhook ID and token","messagePattern":"Discord webhook credential does not contain a webhook ID and token","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-discord.ts","lineNumber":43,"sourceCode":"}\n\nfunction parseWebhook(value: string): DiscordWebhook {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(value);\n\t} catch {\n\t\tthrow new Error(\"Discord webhook credential is not a valid URL\");\n\t}\n\n\tif (url.protocol !== \"https:\") {\n\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\");","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-discord.ts#L25-L61","documentation":"After URL parsing and the HTTPS check, the uploader extracts the webhook ID and token from the path segment after `webhooks` and validates that the ID is numeric. If the path does not contain `/webhooks/<numeric-id>/<token>`, the credential is not a usable Discord webhook and this error is thrown.","triggerScenarios":"The credential is a valid https URL but its path lacks `webhooks` (e.g. a Discord channel or API URL), the ID or token segment is missing, or the ID is non-numeric (e.g. a webhook created via a guild template or an interaction endpoint ` interactions/<id>/... ` URL was pasted instead).","commonSituations":"Pasting a Discord channel invite or API URL instead of the webhook URL; using an incoming-webhook URL from Slack (which has a different path shape) in the discord destination; truncating the URL so the token is cut off; Discord's newer `?wait=` or extra-path variants pasted partially.","solutions":["Use the full Copy Webhook URL: https://discord.com/api/webhooks/<numeric-id>/<token>","Verify both the numeric ID and token segments are present after /webhooks/ in the URL","Ensure you are not pasting a Slack or other-provider webhook URL into the discord destination","Recreate the webhook in Discord if the URL was truncated or the token was rotated"],"exampleFix":"// before (channel URL, not a webhook)\nwebhookUrl = https://discord.com/channels/1234567890/9876543210\n// after\nwebhookUrl = https://discord.com/api/webhooks/1234567890/aBcDeFgHiJkLmNoP","handlingStrategy":"validation","validationCode":"function extractWebhookIdAndToken(webhookUrl) {\n  const url = new URL(webhookUrl);\n  const segs = url.pathname.split('/').filter(Boolean);\n  const i = segs.indexOf('webhooks');\n  const id = i >= 0 ? segs[i + 1] : undefined;\n  const token = i >= 0 ? segs[i + 2] : undefined;\n  if (!id || !token || !/^\\d+$/.test(id)) {\n    throw new Error('URL is not a Discord webhook (need /webhooks/<numeric-id>/<token>)');\n  }\n  return { id, token };\n}","typeGuard":"function isDiscordWebhookUrl(value) {\n  if (typeof value !== 'string') return false;\n  try {\n    const url = new URL(value);\n    const segs = url.pathname.split('/').filter(Boolean);\n    const i = segs.indexOf('webhooks');\n    return i >= 0 && /^\\d+$/.test(segs[i + 1] ?? '') && segs[i + 2] !== undefined;\n  } catch {\n    return false;\n  }\n}","tryCatchPattern":"try {\n  await publishToDiscord(blob);\n} catch (err) {\n  if (err.message.includes('webhook ID and token')) {\n    logger.error('webhookUrl is not a Discord webhook URL — re-copy from Discord Integrations');\n  } else {\n    throw err;\n  }\n}","preventionTips":["Use the exact Copy Webhook URL from Discord, never a channel or API URL","Don't confuse Slack/other webhooks with Discord's /webhooks/<id>/<token> shape","Check the URL wasn't truncated by shell quoting or config escaping","Validate the /webhooks/<numeric-id>/<token> pattern before saving the credential"],"tags":["discord","webhook","config","url-parsing"],"backgroundTag":"invalid-webhook-url","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}