{"record":{"id":"19cc45796c480b16","repo":"zylon-ai/private-gpt","slug":"openapi-spec-url-not-found-in-anthropic-stats-url","errorCode":null,"errorMessage":"openapi_spec_url not found in {ANTHROPIC_STATS_URL}","messagePattern":"openapi_spec_url not found in (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"warning","filePath":"scripts/update_claude_specs.py","lineNumber":26,"sourceCode":"import urllib.request\nfrom pathlib import Path\n\nROOT = Path(__file__).parent.parent\n\nANTHROPIC_STATS_URL = \"https://raw.githubusercontent.com/anthropics/anthropic-sdk-typescript/main/.stats.yml\"\nANTHROPIC_PYPI_URL = \"https://pypi.org/pypi/anthropic/json\"\n\nOPENAPI_TEST_FILE = ROOT / \"tests/models/anthropic/test_openapi_schema.py\"\nPYPROJECT_FILE = ROOT / \"pyproject.toml\"\n\n\ndef fetch_openapi_spec_url() -> str:\n    with urllib.request.urlopen(ANTHROPIC_STATS_URL, timeout=15) as resp:\n        for line in resp.read().decode().splitlines():\n            if line.startswith(\"openapi_spec_url:\"):\n                spec_url: str = line.split(\":\", 1)[1].strip()\n                return spec_url\n    raise RuntimeError(f\"openapi_spec_url not found in {ANTHROPIC_STATS_URL}\")\n\n\ndef fetch_latest_anthropic_version() -> str:\n    with urllib.request.urlopen(ANTHROPIC_PYPI_URL, timeout=15) as resp:\n        version: str = json.loads(resp.read().decode())[\"info\"][\"version\"]\n        return version\n\n\ndef current_openapi_spec_url() -> str:\n    source = OPENAPI_TEST_FILE.read_text(encoding=\"utf-8\")\n    match = re.search(r'^OPENAPI_SPEC_URL\\s*=\\s*\"([^\"]*)\"', source, re.MULTILINE)\n    spec_url: str = match.group(1) if match else \"<not found>\"\n    return spec_url\n\n\ndef current_anthropic_version() -> str:\n    source = PYPROJECT_FILE.read_text(encoding=\"utf-8\")\n    match = re.search(r'\"anthropic>=([\\d.]+)\"', source)","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/scripts/update_claude_specs.py#L8-L44","documentation":"Raised by fetch_openapi_spec_url in scripts/update_claude_specs.py after downloading the anthropic-sdk-python stats file (ANTHROPIC_STATS_URL) and scanning every line for one starting with 'openapi_spec_url:'. The stats file is expected to advertise the URL of the OpenAPI spec used to generate the SDK's typed client; if no such line exists (format change, redirect serving different content, or an error page), the maintenance script cannot proceed.","triggerScenarios":"Anthropic changing the stats-file format (renaming the key or switching to YAML/JSON); the URL serving a redirect page or HTML error (proxy block, rate limit) whose body has no matching line; network middleboxes rewriting the response; the script running after upstream removed the metadata line.","commonSituations":"Scheduled spec-refresh jobs breaking when upstream repo layout changes; corporate proxies returning an HTML block page for raw.githubusercontent URLs; transient GitHub issues serving error bodies with HTTP 200; long-lived forks where the pinned stats URL went stale.","solutions":["Fetch the URL manually (curl -sL <ANTHROPIC_STATS_URL> | grep openapi_spec_url) to see what the file currently contains","If upstream renamed the key, update the prefix check in fetch_openapi_spec_url to the new field name and pin the script to a known-good ref","Handle redirects/auth: use urlopen with the final URL, or add a retry with backoff for transient failures","If the metadata line truly moved, locate the current spec URL in the repo and adjust ANTHROPIC_STATS_URL / the parsing accordingly"],"exampleFix":"# before\nfor line in resp.read().decode().splitlines():\n    if line.startswith(\"openapi_spec_url:\"):\n        ...\n\n# after (diagnose + tolerate format drift)\nbody = urllib.request.urlopen(ANTHROPIC_STATS_URL, timeout=15).read().decode()\nmatch = re.search(r'^\\s*openapi_spec_url\\s*:\\s*(\\S+)', body, re.M)\nif not match:\n    raise RuntimeError(f\"openapi_spec_url not found; body preview: {body[:200]!r}\")\nspec_url = match.group(1)","handlingStrategy":"fallback","validationCode":"import urllib.request\n\ndef stats_url_contains_spec_key(url: str) -> bool:\n    try:\n        with urllib.request.urlopen(url, timeout=15) as resp:\n            return any(\n                line.startswith(\"openapi_spec_url:\")\n                for line in resp.read().decode().splitlines()\n            )\n    except OSError:\n        return False\n\n# if False, pin a known-good ref or update the parser before running the script","typeGuard":"null","tryCatchPattern":"try:\n    spec_url = fetch_openapi_spec_url()\nexcept (RuntimeError, OSError) as e:\n    # fall back to the last known-good URL instead of failing the whole update\n    spec_url = current_openapi_spec_url()\n    logger.warning(\"using cached spec URL %s after fetch failure: %s\", spec_url, e)","preventionTips":["Fetch the stats file and grep it manually before running the refresh job after upstream releases","Pin the script to a known-good upstream commit/ref rather than a moving branch","Cache the last-good spec URL in the repo (as tests already do) and fall back to it on fetch/format failures","Alert on HTML/error bodies (content-type check) so proxy blocks are distinguishable from format drift"],"tags":["scripts","network","openapi","upstream-drift"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}