{"record":{"id":"cd63dca27f54985e","repo":"maboloshi/github-chinese","slug":"retries-last-err","errorCode":null,"errorMessage":"请求失败（重试 {retries} 次后仍失败）：{last_err}","messagePattern":"请求失败（重试 (.+?) 次后仍失败）：(.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"script/ai_review.py","lineNumber":38,"sourceCode":"\ndef fetch(url: str, headers: dict | None = None, retries: int = 3) -> str:\n    \"\"\"拉取 URL；对 5xx / 网络错误做指数退避重试。\"\"\"\n    req = urllib.request.Request(\n        url, headers=headers or {\"User-Agent\": \"github-chinese-ai-review\"}\n    )\n    last_err = \"\"\n    for attempt in range(retries):\n        try:\n            with urllib.request.urlopen(req, timeout=60) as r:\n                return r.read().decode(\"utf-8\")\n        except urllib.error.HTTPError as e:\n            last_err = f\"HTTP {e.code}\"\n            if e.code < 500:  # 4xx 不重试\n                raise\n        except Exception as e:  # noqa: BLE001 - 网络层异常统一重试\n            last_err = str(e)\n        time.sleep(2 * (attempt + 1))\n    raise RuntimeError(f\"请求失败（重试 {retries} 次后仍失败）：{last_err}\")\n\n\ndef main() -> None:\n    parser = argparse.ArgumentParser(description=\"AI 代码审查（DeepSeek）\")\n    parser.add_argument(\"--repo\", required=True, help=\"owner/repo\")\n    parser.add_argument(\"--pr\", required=True, help=\"PR 编号\")\n    parser.add_argument(\"--mode\", choices=[\"full\", \"summary\"], default=\"full\")\n    parser.add_argument(\"--out\", help=\"输出文件（默认 stdout）\")\n    args = parser.parse_args()\n\n    api_key = os.environ.get(\"LLM_API_KEY\")\n    if not api_key:\n        print(\"❌ 缺少环境变量 LLM_API_KEY\", file=sys.stderr)\n        sys.exit(1)\n\n    # 1) PR 元数据 + diff\n    try:\n        pr_meta = json.loads(fetch(f\"https://api.github.com/repos/{args.repo}/pulls/{args.pr}\"))","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/maboloshi/github-chinese/blob/1db777260aeaeba52b39ebc37e1097e0c7053198/script/ai_review.py#L20-L56","documentation":"fetch() is the script's shared HTTP getter with bounded exponential backoff (time.sleep(2*(attempt+1))) over retries attempts (default 3). It only reaches the final raise for 5xx responses or network-layer exceptions (URLError/timeout/SSL) that survive all retries; 4xx (including 401/403/404/429) are re-raised immediately as urllib.error.HTTPError and never produce this RuntimeError. The message reports the last failure reason but, notably, not the URL that failed.","triggerScenarios":"Calling fetch() against api.github.com (PR metadata), github.com (.../pull/N.diff), raw.githubusercontent.com (copilot-instructions.md), and hitting a sustained 5xx from those hosts — or a connection error (DNS, TLS, proxy, read timeout >60s) — for all 3 attempts. Each caller (main()) wraps fetch in its own try/except and exits with a tailored message, so this RuntimeError is the inner cause printed there.","commonSituations":"api.github.com or github.com briefly 5xx-ing; a flaky proxy or VPN dropping mid-request; an aggressive firewall resetting TLS; GitHub rate-limiting returning 429 (re-raised as HTTPError before exhaustion, so not this message); running the script where LLM_BASE_URL/HTTPS_PROXY is mis-set.","solutions":["Retry after a short wait — 5xx and transient network errors often clear; rerun the script once connectivity is stable.","Verify network/proxy: ensure HTTPS_PROXY is set if behind a firewall and that api.github.com resolves (DNS) and TLS completes.","Increase resilience by raising retries and/or the per-request timeout for the github.com diff endpoint, which can be slow on large PRs.","If hitting GitHub auth limits, supply a token header to lift the 60/hr anonymous cap (note: 403/429 surface as HTTPError, not this RuntimeError)."],"exampleFix":"# before\n    raise RuntimeError(f\"请求失败（重试 {retries} 次后仍失败）：{last_err}\")\n# after — include the URL and status so the caller knows which call failed\n    raise RuntimeError(f\"请求失败（重试 {retries} 次后仍失败）：{url} → {last_err}\")","handlingStrategy":"retry","validationCode":"# Before calling fetch(), confirm connectivity to the target host:\nimport socket\nfor host in (\"api.github.com\", \"github.com\"):\n    try:\n        socket.gethostbyname(host)\n    except OSError as e:\n        sys.exit(f\"❌ 无法解析 {host}，请检查 DNS/代理：{e}\")","typeGuard":"null","tryCatchPattern":"try:\n    pr_meta = json.loads(fetch(url, retries=3))\nexcept (RuntimeError, urllib.error.HTTPError) as e:\n    print(f\"❌ PR 拉取失败：{e}\", file=sys.stderr)\n    sys.exit(1)","preventionTips":["Set HTTPS_PROXY in firewalled/VPN environments before running.","Provide a GitHub token (Authorization: Bearer) to avoid anonymous rate limits where relevant.","Keep retries >= 3 and let the existing exponential backoff handle transient 5xx.","Read Retry-After on 429/503 instead of treating every failure as fatal."],"tags":["python","network","retry","http","github-api","script"],"backgroundTag":null,"analyzedSha":"1db777260aeaeba52b39ebc37e1097e0c7053198","analyzedAt":"2026-08-13T05:58:27.900Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}