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
- 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.
- Configure the proxy's bypass list so api.github.com is not intercepted.
- If you run a mirror, ensure it emits the standard release JSON including tag_name.
- 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
- Treat the update check as best-effort: never let it abort the main command.
- Bypass corporate proxies for api.github.com or set HTTPS_PROXY correctly.
- Pin/disable the version check in automation if the network path is untrusted.
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
- ERROR: Feature directory not found. Set SPECIFY_FEATURE_DIRE
- Invalid extension registry {registry}: not a regular file
- Preset '{component.id}' is not bundled and network access is
- Extension '{component.id}' is not bundled and network access
- Workflow '{component.id}' installs from a catalog and networ
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/e168d5f4f0f80f24.
Report an issue: GitHub.