{"record":{"id":"c22034f3008d23ea","repo":"affaan-m/ECC","slug":"invalid-repository","errorCode":null,"errorMessage":"invalid repository","messagePattern":"invalid repository","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/discord/announcement-core.mjs","lineNumber":24,"sourceCode":"  return discussion?.category?.name === 'Announcements';\n}\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 },","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/discord/announcement-core.mjs#L6-L42","documentation":"announcementKey composes a stable 'owner/repo:discussion:<id>' key from a repository string and discussion id. It validates the repository against /^[^/\\s]+\\/[^/\\s]+$/ (exactly one slash, no whitespace) and throws 'invalid repository' when the value is missing, malformed, or contains spaces. This prevents downstream keys and footer references from being ambiguous.","triggerScenarios":"main() passes an input object whose repository field is undefined/null, an empty string, a bare repo name like 'ECC' without owner, a full URL like 'https://github.com/owner/repo', or a value containing whitespace or multiple slashes.","commonSituations":"Parsing the repo from GITHUB_REPOSITORY incorrectly; passing a GitHub URL instead of 'owner/repo'; forgetting to thread the repository through a config object so it arrives undefined.","solutions":["Pass the repository as 'owner/repo' (e.g. 'affaan-m/ECC'), sourced from the GITHUB_REPOSITORY env var or an explicit config field.","If you have a URL, normalize it first: new URL(u).pathname.replace(/^\\//,'').replace(/\\.git$/,'').","Log or validate the config object before calling announcementKey so an undefined repository fails fast with a clear message.","Trim whitespace on the input before constructing the object."],"exampleFix":"// before\nconst key = announcementKey({ repository: 'https://github.com/affaan-m/ECC', discussionId });\n// after\nconst repo = (process.env.GITHUB_REPOSITORY || 'affaan-m/ECC').trim();\nconst key = announcementKey({ repository: repo, discussionId });","handlingStrategy":"validation","validationCode":"const isValidRepo = (repo) => typeof repo === 'string' && /^[^\\/\\s]+\\/[^\\/\\s]+$/.test(repo.trim());\nif (!isValidRepo(process.env.GITHUB_REPOSITORY)) throw new Error('Expected repository as owner/repo');","typeGuard":"const isOwnerRepo = (v) => typeof v === 'string' && /^[^\\/\\s]+\\/[^\\/\\s]+$/.test(v);","tryCatchPattern":"try {\n  const key = announcementKey({ repository, discussionId });\n} catch (err) {\n  if (err.message === 'invalid repository') {\n    console.error(`repository must be 'owner/repo', got: ${JSON.stringify(repository)}`);\n  }\n  throw err;\n}","preventionTips":["Source the repository from GITHUB_REPOSITORY rather than parsing URLs by hand.","Normalize any URL to owner/repo before calling announcementKey.","Trim inputs and reject undefined early with a descriptive error.","Unit-test announcementKey with URL-shaped and empty inputs."],"tags":["validation","github","format"],"backgroundTag":"invalid-argument-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"}