{"record":{"id":"893124e169d8216f","repo":"google-gemini/gemini-cli","slug":"failed-to-fetch-pr-pr-number-from-github-api","errorCode":null,"errorMessage":"Failed to fetch PR #{pr_number} from GitHub API ({resp.status_code}): {resp.text}","messagePattern":"Failed to fetch PR #(.+?) from GitHub API \\((.+?)\\): (.+?)","errorType":"http","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"tools/caretaker-agent/evals/triage/helpers/github_api.py","lineNumber":49,"sourceCode":"    data = resp.json()\n    return {\n        \"owner\": owner,\n        \"repo\": repo,\n        \"number\": data.get(\"number\"),\n        \"title\": data.get(\"title\", \"\"),\n        \"body\": data.get(\"body\", \"\") or \"\",\n        \"createdAt\": data.get(\"created_at\", \"\"),\n        \"labels\": data.get(\"labels\", [])\n    }\n\n\ndef get_pr_details(owner: str, repo: str, pr_number: int) -> Dict[str, Any]:\n    \"\"\"Queries GitHub REST API for PR details (title, body, baseRefOid, patch/diff).\"\"\"\n    url = f\"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}\"\n    headers = _get_github_headers()\n    resp = requests.get(url, headers=headers, timeout=15)\n    if resp.status_code != 200:\n        raise RuntimeError(f\"Failed to fetch PR #{pr_number} from GitHub API ({resp.status_code}): {resp.text}\")\n    \n    data = resp.json()\n    \n    # Fetch unified patch/diff\n    diff_url = f\"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}\"\n    diff_headers = headers.copy()\n    diff_headers[\"Accept\"] = \"application/vnd.github.v3.diff\"\n    diff_resp = requests.get(diff_url, headers=diff_headers, timeout=15)\n    diff_content = diff_resp.text if diff_resp.status_code == 200 else \"\"\n\n    return {\n        \"number\": data.get(\"number\"),\n        \"title\": data.get(\"title\", \"\"),\n        \"body\": data.get(\"body\", \"\") or \"\",\n        \"baseRefOid\": data.get(\"base\", {}).get(\"sha\", \"\"),\n        \"diff\": diff_content\n    }\n","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/google-gemini/gemini-cli/blob/5024443c7217464a66e98f80d73172a26440bd8f/tools/caretaker-agent/evals/triage/helpers/github_api.py#L31-L67","documentation":"This RuntimeError is raised by get_pr_details() when the GitHub REST API GET to /repos/{owner}/{repo}/pulls/{pr_number} returns a non-200 status. The function fetches PR metadata first, then a separate diff request; this guard is on the metadata fetch. The error message surfaces the status code and body to diagnose auth, rate-limit, or not-found conditions.","triggerScenarios":"Calling get_pr_details(owner, repo, pr_number) with a non-existent PR number (404), an expired token (401), a rate-limited token (403/429), or a pr_number that is actually an issue (the pulls endpoint returns 404 for non-PRs). The owner/repo is private and no token is set.","commonSituations":"Confusing an issue number with a PR number. Token lacking repo scope for a private repo. GitHub rate limit exhausted after fetching many issues in a batch eval. Stale PR number after the PR was deleted or the repo renamed.","solutions":["Confirm the PR exists: open https://github.com/{owner}/{repo}/pull/{pr_number} in a browser.","Set/refresh GITHUB_TOKEN with repo scope for private repositories.","If rate-limited (403/429), reduce concurrency or wait for X-RateLimit-Reset.","Ensure you pass a PR number, not an issue number; use get_issue_details for issues.","Add retry with backoff for 5xx transient failures."],"exampleFix":"# before: passing an issue number where a PR is expected\npr = get_pr_details(owner, repo, issue_number)\n# after: resolve the linked PR number first\npr = get_pr_details(owner, repo, linked_pr_number)","handlingStrategy":"retry","validationCode":"import os, requests\n\ndef can_fetch_pr(owner: str, repo: str, pr_number: int) -> bool:\n    headers = {'Accept': 'application/vnd.github.v3+json'}\n    token = os.environ.get('GITHUB_TOKEN') or os.environ.get('GH_TOKEN')\n    if token:\n        headers['Authorization'] = f'Bearer {token}'\n    resp = requests.get(f'https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}', headers=headers, timeout=15)\n    return resp.status_code == 200","typeGuard":null,"tryCatchPattern":"import time\nfrom evals.triage.helpers.github_api import get_pr_details\n\nfor attempt in range(3):\n    try:\n        pr = get_pr_details(owner, repo, pr_number)\n        break\n    except RuntimeError as e:\n        if '403' in str(e) or '429' in str(e):\n            time.sleep(2 ** attempt)\n        else:\n            raise\nelse:\n    raise RuntimeError(f'Could not fetch PR {pr_number} after retries')","preventionTips":["Confirm pr_number is a PR (not an issue) before calling get_pr_details.","Set and refresh GITHUB_TOKEN with repo scope for private repos.","Watch rate-limit headers and throttle concurrency accordingly.","Cache PR JSON to avoid repeated fetches."],"tags":["github-api","network","rate-limit","auth","evals","python"],"backgroundTag":null,"analyzedSha":"5024443c7217464a66e98f80d73172a26440bd8f","analyzedAt":"2026-08-12T06:01:53.711Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}