github/spec-kit · warning · ValueError

GitHub API response missing valid tag_name

Error message

GitHub API response missing valid tag_name

What it means

Part of the CLI's version-resolution check: it fetches GitHub's latest-release API (api.github.com/repos/.../releases/latest) with a 5s timeout and size-limited body, parses JSON, and requires a non-empty string tag_name. A 200 response whose JSON lacks a usable tag_name raises this ValueError; HTTP 403/429 and network failures are handled separately as rate-limited/offline results.

Source

Thrown at src/specify_cli/_version.py:133

    """
    from .authentication.http import open_url

    try:
        with open_url(
            GITHUB_API_LATEST,
            timeout=5,
            extra_headers={"Accept": "application/vnd.github+json"},
        ) as resp:
            payload = json.loads(
                read_response_limited(
                    resp,
                    max_bytes=MAX_JSON_METADATA_BYTES,
                    label="GitHub latest release",
                ).decode("utf-8")
            )
            tag = payload.get("tag_name")
            if not isinstance(tag, str) or not tag:
                raise ValueError("GitHub API response missing valid tag_name")
            return tag, None
    except urllib.error.HTTPError as e:
        # Order matters: HTTPError is a subclass of URLError.
        # 403 (primary rate limit / abuse detection) and 429 (Too Many Requests /
        # secondary rate limit) both get the actionable "configure a token" hint;
        # every other status is surfaced verbatim as "HTTP {code}".
        if e.code in (403, 429):
            return None, _RESOLUTION_FAILURE_RATE_LIMITED
        return None, f"{_RESOLUTION_FAILURE_HTTP_PREFIX}{e.code}"
    except (urllib.error.URLError, OSError):
        return None, _RESOLUTION_FAILURE_OFFLINE


def _parse_version_text(value: str) -> Version | None:
    """Parse version-like text after tag normalization, or return None."""
    normalized = _normalize_tag(value)
    try:
        return Version(normalized)

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Test the endpoint your machine actually reaches: curl -sS -H 'Accept: application/vnd.github+json' https://api.github.com/repos/github/spec-kit/releases/latest | jq .tag_name — if it is not a tag, fix proxy/DNS.
  2. Configure the proxy's bypass list so api.github.com is not intercepted.
  3. If you run a mirror, ensure it emits the standard release JSON including tag_name.
  4. As a caller, catch ValueError around version resolution (or disable the update check) so a malformed response cannot crash your command.

Example fix

# before
latest = resolve_latest_version()  # ValueError escapes on malformed 200
# after
try:
    latest = resolve_latest_version()
except ValueError:
    latest = None  # skip update check, continue
Defensive patterns

Strategy: fallback

Validate before calling

import json, urllib.request

def github_release_has_tag(url: str) -> bool:
    try:
        with urllib.request.urlopen(url, timeout=5) as r:
            payload = json.loads(r.read(1 << 20).decode("utf-8"))
        return isinstance(payload.get("tag_name"), str) and bool(payload["tag_name"])
    except Exception:
        return False

Type guard

from typing import Any

def has_valid_tag_name(payload: Any) -> bool:
    """Narrow a GitHub release JSON payload to one with a usable tag_name."""
    return (
        isinstance(payload, dict)
        and isinstance(payload.get("tag_name"), str)
        and bool(payload["tag_name"])
    )

Try / catch

try:
    tag = fetch_latest_tag()
except ValueError:
    tag = None  # malformed upstream response; skip update check, don't crash
except (urllib.error.URLError, OSError):
    tag = None  # offline

Prevention

When it happens

Trigger: An intercepting proxy, captive portal, or misconfigured DNS returns HTTP 200 with an HTML/JSON body that parses as JSON but has no tag_name key (or tag_name is null/number); a GitHub Enterprise mirror or rewritten API URL that returns a different schema.

Common situations: Corporate proxies returning a 200 'login page' JSON; a repository that has no releases (API returns {'message': 'Not Found'} with 200 in some mirrors); future API schema changes; the raised ValueError is not among the caught HTTPError/URLError/OSError, so it can escape as an unhandled crash during version checks.

Related errors


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/e168d5f4f0f80f24. Report an issue: GitHub.