{"record":{"id":"c5e920c41f91b470","repo":"affaan-m/ECC","slug":"invalid-discussion-id","errorCode":null,"errorMessage":"invalid discussion id","messagePattern":"invalid discussion id","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/discord/announcement-core.mjs","lineNumber":25,"sourceCode":"}\n\nexport function releaseMarker(tag) {\n  const normalized = String(tag || '').trim();\n  if (!normalized) throw new Error('release tag is required');\n  return `<!-- ecc-release:${normalized} -->`;\n}\n\nexport function findReleaseDiscussion(discussions, marker) {\n  return discussions.find(item => (\n    item?.category?.name === 'Announcements'\n    && typeof item.body === 'string'\n    && item.body.includes(marker)\n  )) || null;\n}\n\nexport function announcementKey({ repository, discussionId }) {\n  if (!/^[^/\\s]+\\/[^/\\s]+$/.test(String(repository || ''))) throw new Error('invalid repository');\n  if (!/^[A-Za-z0-9_-]+$/.test(String(discussionId || ''))) throw new Error('invalid discussion id');\n  return `${repository}:discussion:${discussionId}`;\n}\n\nexport function buildDiscordPayload({ title, body, url, key }) {\n  const discussionId = String(key).split(':').at(-1);\n  const footer = `ecc:${discussionId}`;\n  const description = String(body || '').trim().slice(0, DISCORD_DESCRIPTION_LIMIT);\n  const nonce = `ecc-${createHash('sha256').update(String(key)).digest('hex').slice(0, 16)}`;\n  return {\n    allowed_mentions: { parse: [] },\n    nonce,\n    enforce_nonce: true,\n    embeds: [{\n      title: String(title || 'ECC announcement').trim().slice(0, 256),\n      description,\n      url: String(url || ''),\n      footer: { text: footer },\n    }],","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/discord/announcement-core.mjs#L7-L43","documentation":"announcementKey validates the discussion id against /^[A-Za-z0-9_-]+$/ (alphanumerics, underscore, hyphen only) and throws 'invalid discussion id' on failure. Discussion ids from the GitHub API are numeric strings, so any other shape means the value was corrupted, decorated with prefixes, or never supplied.","triggerScenarios":"main() supplies discussionId as a URL ('.../discussions/42'), a '#123' or 'D123' prefixed value, a value containing spaces, or undefined when the discussion lookup failed and callers proceed to key-building anyway.","commonSituations":"Copying a discussion URL fragment including '#'; storing the id with formatting characters; a failed findReleaseDiscussion returning null that gets stringified into 'null'.","solutions":["Pass the raw numeric discussion id as a string, e.g. String(discussion.id) from the GraphQL discussion node, not a URL or prefixed value.","Strip prefixes/fragments before calling: discussionId.replace(/^.*[#/]/, '').","Verify the discussion lookup succeeded (non-null) before building the key.","Trim whitespace and confirm with /\\d+/ as a pre-check."],"exampleFix":"// before\nconst key = announcementKey({ repository, discussionId: 'https://github.com/owner/repo/discussions/42#discussion-42' });\n// after\nconst id = String(discussion.id); // e.g. '42' from the API node\nconst key = announcementKey({ repository, discussionId: id });","handlingStrategy":"validation","validationCode":"const isValidDiscussionId = (id) => typeof id === 'string' && /^[A-Za-z0-9_-]+$/.test(id.trim());\nif (!isValidDiscussionId(rawId)) throw new Error('discussionId must be a bare id string');","typeGuard":"const isDiscussionId = (v) => typeof v === 'string' && /^[A-Za-z0-9_-]+$/.test(v);","tryCatchPattern":"try {\n  const key = announcementKey({ repository, discussionId });\n} catch (err) {\n  if (err.message === 'invalid discussion id') {\n    console.error(`discussionId must be a bare id (got ${JSON.stringify(discussionId)})`);\n  }\n  throw err;\n}","preventionTips":["Always use String(discussion.id) from the GraphQL node, never a URL or title.","Strip '#' fragments and prefixes when ids originate from user input.","Confirm the discussion lookup returned a non-null result before key-building.","Trim whitespace from ids that pass through config or env."],"tags":["validation","github","format"],"backgroundTag":"invalid-identifier-format","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}