{"record":{"id":"7aa186cd3963f8a0","repo":"mvanhorn/last30days-skill","slug":"gemini-http-exc-code-detail","errorCode":null,"errorMessage":"Gemini HTTP {exc.code}: {detail}","messagePattern":"Gemini HTTP (.+?): (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/evaluate_search_quality.py","lineNumber":231,"sourceCode":"\n\ndef call_gemini_judge(api_key: str, model: str, prompt: str) -> dict[str, Any]:\n    body = {\n        \"contents\": [{\"parts\": [{\"text\": prompt}]}],\n        \"generationConfig\": {\"temperature\": 0, \"responseMimeType\": \"application/json\"},\n    }\n    request = Request(\n        GEMINI_API_URL.format(model=model, api_key=api_key),\n        data=json.dumps(body).encode(\"utf-8\"),\n        headers={\"Content-Type\": \"application/json\"},\n        method=\"POST\",\n    )\n    try:\n        with urlopen(request, timeout=120) as response:\n            payload = json.loads(response.read().decode(\"utf-8\"))\n    except HTTPError as exc:\n        detail = exc.read().decode(\"utf-8\", errors=\"replace\")\n        raise RuntimeError(f\"Gemini HTTP {exc.code}: {detail}\") from exc\n    except URLError as exc:\n        raise RuntimeError(f\"Gemini request failed: {exc}\") from exc\n    return json.loads(extract_gemini_text(payload))\n\n\ndef build_judge_prompt(topic: str, query_type: str, items: list[dict[str, Any]]) -> str:\n    item_lines = []\n    for item in items:\n        item_lines.append(\n            \"\\n\".join([\n                f\"- id: {item['key']}\",\n                f\"  source: {item['source']}\",\n                f\"  title: {item['text'][:220]}\",\n                f\"  url: {item['url']}\",\n                f\"  date: {item.get('date') or 'unknown'}\",\n            ])\n        )\n    return f\"\"\"","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/evaluate_search_quality.py#L213-L249","documentation":"call_gemini_judge() wraps urllib's HTTPError from urlopen(request, timeout=120) into a RuntimeError that embeds the HTTP status code and the raw error body read from the response stream. It fires only for non-2xx HTTP responses from the Gemini REST endpoint — anything the API rejects with 4xx/5xx.","triggerScenarios":"urlopen raising HTTPError: 400 (malformed JSON body or bad model name in GEMINI_API_URL.format), 401/403 (invalid or unmapped API key), 404 (unknown model id), 429 (rate limit), 5xx (server error). The api_key is interpolated directly into the URL, so a wrong key yields 400/403.","commonSituations":"GEMINI_API_KEY / GOOGLE_GENAI_API_KEY env var missing or set to a placeholder; using a deprecated model name; hitting free-tier RPM limits while scoring many topics; transient 500s from the generateContent endpoint.","solutions":["Match on the embedded status: 400/403 means bad key or model — fix GEMINI_API_KEY/GOOGLE_GENAI_API_KEY or the --judge-model value; 404 means the model id in GEMINI_API_URL is wrong for your key.","For 429: add exponential backoff retry around call_gemini_judge (honor retryDelay in the detail body).","For 5xx: retry once or twice with the same backoff; the call is idempotent at temperature 0.","Never log the URL — it contains the API key; log only exc.code and detail."],"exampleFix":"# before\nresult = call_gemini_judge(api_key, model, prompt)\n\n# after\nfor attempt in range(3):\n    try:\n        result = call_gemini_judge(api_key, model, prompt)\n        break\n    except RuntimeError as exc:\n        if 'HTTP 429' not in str(exc) and 'HTTP 5' not in str(exc):\n            raise\n        if attempt == 2:\n            raise\n        time.sleep(2 ** attempt)","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for attempt in range(3):\n    try:\n        return call_gemini_judge(api_key, model, prompt)\n    except RuntimeError as exc:\n        transient = 'HTTP 429' in str(exc) or 'HTTP 500' in str(exc) or 'HTTP 503' in str(exc)\n        if not transient or attempt == 2:\n            raise\n        time.sleep(2 ** attempt)","preventionTips":["Never embed the api_key in logs — the URL carries it; log exc.code only.","Verify GEMINI_API_KEY/GOOGLE_GENAI_API_KEY resolve via the same env/config chain the script uses before batch runs.","Rate-limit judge calls (sleep between topics) to stay under free-tier RPM."],"tags":["gemini","http","api-key","rate-limit"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}