{"record":{"id":"3c111459c92a226e","repo":"nikivdev/code","slug":"unable-to-parse-github-repo-from-3c1114","errorCode":null,"errorMessage":"unable to parse GitHub repo from: {}","messagePattern":"unable to parse GitHub repo from: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/repos.rs","lineNumber":1747,"sourceCode":"    } else if let Some(idx) = trimmed.find(\"github.com/\") {\n        &trimmed[idx + \"github.com/\".len()..]\n    } else {\n        trimmed\n    };\n\n    let path = path\n        .trim_start_matches('/')\n        .split(&['?', '#'][..])\n        .next()\n        .unwrap_or(path)\n        .trim_end_matches('/');\n\n    let mut parts = path.split('/');\n    let owner = parts.next().unwrap_or(\"\").trim();\n    let repo = parts.next().unwrap_or(\"\").trim();\n\n    if owner.is_empty() || repo.is_empty() {\n        bail!(\"unable to parse GitHub repo from: {}\", input);\n    }\n\n    let repo = repo.strip_suffix(\".git\").unwrap_or(repo);\n    if repo.is_empty() {\n        bail!(\"unable to parse GitHub repo from: {}\", input);\n    }\n\n    Ok(RepoRef {\n        owner: owner.to_string(),\n        repo: repo.to_string(),\n    })\n}\n\npub(crate) fn normalize_root(raw: &str) -> Result<PathBuf> {\n    let expanded = config::expand_path(raw);\n    let cwd = std::env::current_dir().context(\"failed to resolve current directory\")?;\n    let root = if expanded.is_absolute() {\n        expanded","sourceCodeStart":1729,"sourceCodeEnd":1765,"githubUrl":"https://github.com/nikivdev/code/blob/a747e741ae92c09071d0ae946ab48488adcff1ce/src/repos.rs#L1729-L1765","documentation":"After deriving the path portion of a GitHub reference, parse_github_repo splits it on '/' expecting an owner and a repo name. If the first or second segment is missing/empty (e.g. input reduced to just \"owner\" or \"owner/\"), it bails because a GitHub repository requires both parts.","triggerScenarios":"Calling parse_github_repo with \"owner\" (no slash), \"owner/\" (empty repo segment), \"/repo\" (empty owner), or a github.com URL whose path lacks owner/repo depth such as \"https://github.com/owner\".","commonSituations":"Passing just a username instead of owner/repo; truncation when copying a URL; a URL to a GitHub user profile or org page rather than a repository; config template with only the owner filled in.","solutions":["Provide both segments: owner/repo (optionally with .git, which is stripped automatically)","Verify the input points to a repository, not a user/org/profile page","Add a caller-side check that the string contains exactly owner/repo before parsing"],"exampleFix":"// before\nlet r = parse_github_repo(\"https://github.com/torvalds\")?;\n// after\nlet r = parse_github_repo(\"https://github.com/torvalds/linux\")?;","handlingStrategy":"validation","validationCode":"fn is_owner_repo_pair(input: &str) -> bool {\n    let t = input.trim().trim_end_matches(\".git\");\n    let t = t.rsplit_once(\"github.com/\").map_or(t, |(_, rest)| rest);\n    let mut parts = t.trim_matches('/').split('/').filter(|p| !p.is_empty());\n    matches!((parts.next(), parts.next()), (Some(o), Some(r)) if !o.is_empty() && !r.is_empty())\n}","typeGuard":"fn has_owner_and_repo(input: &str) -> bool {\n    input.contains('/') && !input.trim_end_matches(\"/\").ends_with('/')\n}","tryCatchPattern":"let r = parse_github_repo(input)\n    .map_err(|e| anyhow!(\"expected owner/repo, got {:?}: {e}\", input))?;","preventionTips":["Always pass both owner and repo segments","Verify URLs point at repositories, not user/org pages","Check for truncation when copying repo URLs","Normalize inputs to owner/repo shorthand in scripts"],"tags":["input-validation","github","url-parsing"],"backgroundTag":"invalid-repository-url","analyzedSha":"a747e741ae92c09071d0ae946ab48488adcff1ce","analyzedAt":"2026-09-01T22:43:55.719Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}