{"record":{"id":"e8941ce5fb69ec3a","repo":"google-gemini/gemini-cli","slug":"invalid-github-repository-source-source-expec","errorCode":null,"errorMessage":"Invalid GitHub repository source: ${source}. Expected \"owner/repo\" or a github repo uri.","messagePattern":"Invalid GitHub repository source: (.+?)\\. Expected \"owner/repo\" or a github repo uri\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/config/extensions/github.ts","lineNumber":122,"sourceCode":"    // Throw a TypeError to maintain a consistent error contract for invalid URLs.\n    // This avoids a breaking change for consumers who might expect a TypeError.\n    throw new TypeError(`Invalid repo URL: ${source}`, { cause: e });\n  }\n\n  if (!parsedUrl) {\n    throw new Error(`Invalid repo URL: ${source}`);\n  }\n  if (parsedUrl?.host !== 'github.com') {\n    return null;\n  }\n  // The pathname should be \"/owner/repo\".\n  const parts = parsedUrl?.pathname\n    .split('/')\n    // Remove the empty segments, fixes trailing and leading slashes\n    .filter((part) => part !== '');\n\n  if (parts?.length !== 2) {\n    throw new Error(\n      `Invalid GitHub repository source: ${source}. Expected \"owner/repo\" or a github repo uri.`,\n    );\n  }\n  const owner = parts[0];\n  const repo = parts[1].replace('.git', '');\n\n  return {\n    owner,\n    repo,\n  };\n}\n\nexport async function fetchReleaseFromGithub(\n  owner: string,\n  repo: string,\n  ref?: string,\n  allowPreRelease?: boolean,\n): Promise<GithubReleaseData | null> {","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/google-gemini/gemini-cli/blob/5024443c7217464a66e98f80d73172a26440bd8f/packages/cli/src/config/extensions/github.ts#L104-L140","documentation":"Thrown by tryParseGithubUrl() when the URL parses and is on github.com but the pathname does not split into exactly two non-empty segments (owner/repo). Sources with one segment (just 'owner') or three+ (owner/repo/extra/path) both fail this check.","triggerScenarios":"Source like 'someuser' (one segment), 'owner/repo/tree/main' (extra path segments), or a github URL pointing at a non-repo page (e.g., a gist or a settings page) whose pathname has more than two parts.","commonSituations":"User pastes a URL to a file inside a repo (owner/repo/blob/main/x.ts) instead of the repo root; mistyped shorthand missing the repo name; trailing slashes combined with extra path.","solutions":["Use the bare 'owner/repo' shorthand or the repository root URL.","If you must accept deep links, pre-process the URL to keep only the first two path segments.","Validate the segment count before calling and surface a friendly message."],"exampleFix":"// before\nconst info = tryParseGithubUrl('owner/repo/blob/main/file.ts');\n// after\nconst info = tryParseGithubUrl('owner/repo');","handlingStrategy":"validation","validationCode":"function githubPathSegments(source: string): string[] | null {\n  try { const u = new URL(source, 'https://github.com'); if (u.host !== 'github.com') return null; return u.pathname.split('/').filter(Boolean); } catch { return null; }\n}","typeGuard":"function isOwnerRepoPair(segments: string[]): boolean { return segments.length === 2; }","tryCatchPattern":"try { tryParseGithubUrl(src); } catch (e) { if (/Expected \"owner\\/repo\"/.test(String(e))) { /* suggest using repo root URL */ } throw e; }","preventionTips":["Store/accept the repo-root URL or 'owner/repo' shorthand, not deep file links.","Pre-slice deep links to the first two path segments."],"tags":["extensions","git","validation","url-parsing"],"backgroundTag":null,"analyzedSha":"5024443c7217464a66e98f80d73172a26440bd8f","analyzedAt":"2026-08-12T06:01:53.711Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}