{"record":{"id":"5e07516d952d79c1","repo":"Hmbown/CodeWhale","slug":"gh-pr-view-did-not-return-exact-base-and-head-commit-ids","errorCode":null,"errorMessage":"gh pr view did not return exact base and head commit IDs","messagePattern":"gh pr view did not return exact base and head commit IDs","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/review_pr.rs","lineNumber":90,"sourceCode":"        return false;\n    };\n    commit_id(old) && commit_id(new) && old.len() == new.len()\n}\n\nfn view_with(\n    number: u32,\n    repo: Option<&str>,\n    run: &mut impl FnMut(Program, &[String]) -> Result<String>,\n) -> Result<GhPullRequest> {\n    if number == 0 {\n        bail!(\"A positive pull request number is required\");\n    }\n    let mut args = pr_args(\"view\", number, repo);\n    args.extend([\"--json\".into(), VIEW_FIELDS.into()]);\n    let view: GhPullRequest = serde_json::from_str(&run(Program::Gh, &args)?)\n        .context(\"gh pr view returned incomplete PR metadata\")?;\n    if !commit_id(&view.base_sha) || !commit_id(&view.head_sha) {\n        bail!(\"gh pr view did not return exact base and head commit IDs\");\n    }\n    Ok(view)\n}\n\npub(crate) fn fetch_view(\n    number: u32,\n    repo: Option<&str>,\n    workspace: &Path,\n) -> Result<GhPullRequest> {\n    view_with(number, repo, &mut |program, args| {\n        run_command(workspace, program, args)\n    })\n}\n\nfn same_revision(expected: &GhPullRequest, current: &GhPullRequest) -> Result<()> {\n    if expected.head_sha != current.head_sha\n        || expected.base_sha != current.base_sha\n        || expected.changed_files != current.changed_files","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tools/review_pr.rs#L72-L108","documentation":"view_with shells out to `gh pr view --json <fields>` and deserializes the JSON into GhPullRequest. After parsing it validates that base_sha and head_sha are exact 40-char commit IDs via commit_id(); if either is missing, abbreviated, or otherwise not an exact SHA, the review tool refuses the PR because all later diff/receipt logic pins to exact base/head commits.","triggerScenarios":"`gh pr view` returns JSON whose baseRefOid or headRefOid is empty, abbreviated, or not a full 40-hex commit; the deserialize succeeds but the commit_id() check fails.","commonSituations":"Older or nonstandard gh CLI versions that omit/shorten oid fields; GitHub Enterprise with proxies that rewrite responses; a PR whose base branch was deleted so the base oid resolves oddly; corporate wrappers around gh emitting different JSON shapes.","solutions":["Run `gh pr view <number> --json baseRefOid,headRefOid` manually and confirm both fields contain full 40-character SHAs","Upgrade or repair the gh CLI to a version that returns exact oids (`gh --version`); remove any wrapper script shadowing gh in PATH","Re-push the PR branch so headRefOid resolves, and ensure the base branch still exists on the remote"],"exampleFix":"// before: trusting whatever gh returned\nlet view: GhPullRequest = serde_json::from_str(&run(Program::Gh, &args)?)?;\n// after: fail fast with a clear message when oids are missing\nlet view: GhPullRequest = serde_json::from_str(&run(Program::Gh, &args)?)\n    .context(\"gh pr view returned incomplete PR metadata\")?;\nif !commit_id(&view.base_sha) || !commit_id(&view.head_sha) {\n    bail!(\"gh pr view did not return exact base and head commit IDs\");\n}","handlingStrategy":"validation","validationCode":"// check gh output before handing to the review tool\nlet out = std::process::Command::new(\"gh\").args([\"pr\",\"view\",&num,\"--json\",\"baseRefOid,headRefOid\"]).output()?;\nlet v: serde_json::Value = serde_json::from_slice(&out.stdout)?;\nlet is_full_sha = |s: &str| s.len() == 40 && s.chars().all(|c| c.is_ascii_hexdigit());\nassert!(is_full_sha(v[\"baseRefOid\"].as_str().unwrap_or(\"\")) && is_full_sha(v[\"headRefOid\"].as_str().unwrap_or(\"\")), \"gh did not return full commit SHAs\");","typeGuard":"fn commit_id(s: &str) -> bool { s.len() == 40 && s.chars().all(|c| c.is_ascii_hexdigit()) }","tryCatchPattern":"match view_with(number, repo) {\n    Err(e) if e.to_string().contains(\"exact base and head commit IDs\") => eprintln!(\"gh returned incomplete metadata; upgrade gh and retry\"),\n    other => other?,\n}","preventionTips":["Pin a recent gh CLI version and verify `gh --version` in setup scripts","Pre-flight `gh pr view <n> --json baseRefOid,headRefOid` and assert full 40-char SHAs","Avoid wrapper scripts that reformat gh's JSON output"],"tags":["git","github-cli","external-command","validation"],"backgroundTag":"unexpected-response-shape","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}