{"record":{"id":"a735eb7339fd5272","repo":"jackwener/OpenCLI","slug":"github-trending-parser-drift-invalid-repository-i","errorCode":null,"errorMessage":"github-trending parser drift: invalid repository identity \"${repo}\"","messagePattern":"github-trending parser drift: invalid repository identity \"(.+?)\"","errorType":"error_code","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/github-trending/repos.js","lineNumber":69,"sourceCode":"    const blocks = Array.from(String(html ?? '').matchAll(/<article\\b[^>]*class=\"[^\"]*\\bBox-row\\b[^\"]*\"[^>]*>([\\s\\S]*?)<\\/article>/g))\n        .map((match) => match[1]);\n    const rows = [];\n\n    if (blocks.length === 0) {\n        if (hasExplicitEmptyTrending(html)) return rows;\n        throw new CommandExecutionError('github-trending parser drift: no repository rows found');\n    }\n\n    for (const raw of blocks) {\n        const block = raw;\n\n        const nameMatch = block.match(/<h2\\b[\\s\\S]*?href=\"\\/([^\"/?#]+\\/[^\"/?#]+)\"/);\n        if (!nameMatch) {\n            throw new CommandExecutionError('github-trending parser drift: missing repository link');\n        }\n        const repo = decodeHtmlEntities(nameMatch[1]).trim();\n        if (!/^[A-Za-z0-9_.-]+\\/[A-Za-z0-9_.-]+$/.test(repo)) {\n            throw new CommandExecutionError(`github-trending parser drift: invalid repository identity \"${repo}\"`);\n        }\n\n        const descMatch = block.match(/<p class=\"col-9 color-fg-muted[^\"]*\">([\\s\\S]*?)<\\/p>/);\n        const description = descMatch\n            ? decodeHtmlEntities(stripTags(descMatch[1]).replace(/\\s+/g, ' ')).trim()\n            : '';\n\n        const langMatch = block.match(/<span itemprop=\"programmingLanguage\">([\\s\\S]*?)<\\/span>/);\n        const language = langMatch ? decodeHtmlEntities(stripTags(langMatch[1])).trim() : null;\n\n        const escapedRepo = escapeRegExp(repo);\n        const starsMatch = block.match(new RegExp(`<a\\\\b[^>]*href=\"/${escapedRepo}/stargazers\"[^>]*>([\\\\s\\\\S]*?)</a>`));\n        const forksMatch = block.match(new RegExp(`<a\\\\b[^>]*href=\"/${escapedRepo}/forks\"[^>]*>([\\\\s\\\\S]*?)</a>`));\n        const sinceMatch = block.match(/([\\d,]+)\\s+stars\\s+(?:today|this week|this month)/i);\n\n        rows.push({\n            repo,\n            description,","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/github-trending/repos.js#L51-L87","documentation":"After extracting the owner/repo path from a trending row's link, the parser validates it against `^[A-Za-z0-9_.-]+\\/[A-Za-z0-9_.-]+$`. If the extracted value doesn't look like a valid owner/repo pair, it throws this CommandExecutionError — an integrity check that the regex didn't capture garbage (e.g. a non-repository link) after markup changes.","triggerScenarios":"parseTrendingHtml extracts an href from a row's h2, but after entity decoding and trimming the string fails the owner/repo pattern — for example an href to '/trending/...', '/features/...', or an oddly-encoded path captured because the h2 regex matched a broader link after markup drift.","commonSituations":"GitHub adds non-repo links inside the heading area (badges, org links); URL-encoded characters (e.g. %20) appearing in captured paths; markup drift causing the regex to capture the wrong href.","solutions":["Upgrade the scraper package for updated regexes matching current markup","Check the captured value in the message to see what the regex matched and adjust the pattern","Retry later if GitHub is mid-deploy"],"exampleFix":"// before\nif (!/^[A-Za-z0-9_.-]+\\/[A-Za-z0-9_.-]+$/.test(repo)) throw new CommandExecutionError(...);\n// after: decode + validate with decoded URI\nconst repo = decodeURIComponent(decodeHtmlEntities(nameMatch[1])).trim();","handlingStrategy":"try-catch","validationCode":"const REPO_RE = /^[A-Za-z0-9_.-]+\\/[A-Za-z0-9_.-]+$/;\nfunction looksLikeRepoPath(p) { return REPO_RE.test(decodeURIComponent(String(p)).trim()); }","typeGuard":"function isInvalidRepoIdentity(e) { return e instanceof Error && /invalid repository identity/.test(e.message); }","tryCatchPattern":"try {\n  const rows = parseTrendingHtml(html);\n} catch (e) {\n  if (isInvalidRepoIdentity(e)) { console.error('Scraper captured a non-repo link:', e.message); throw e; }\n  throw e;\n}","preventionTips":["URI-decode captured paths before validating in local patches","Keep the owner/repo validation regex in sync with GitHub URL conventions","Report recurring captures of non-repo links upstream with sample HTML"],"tags":["scraping","parser-drift","github","validation"],"backgroundTag":"parser-drift-html-structure-changed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}