HKUDS/DeepTutor · error · HubError
{self.name}: catalog returned invalid JSON
Error message
{self.name}: catalog returned invalid JSON What it means
Raised by HubProvider.catalog when the HTTP response body cannot be parsed as JSON (response.json() raises ValueError). The catalog endpoint returned HTML, plain text, or an empty body — typically a gateway error page or wrong base_url.
Source
Thrown at deeptutor/services/skill/hub.py:408
def catalog(
self, *, query: str = "", limit: int = 50, sort: str = "createdAt"
) -> list[dict[str, Any]]:
"""List skills published on the hub, for the in-app skill browser.
Uses ``/search`` when a query is given (server-side relevance) and the
plain ``/skills`` explore feed otherwise. Rows carry display name,
summary, version, download/star counts and owner — everything the
DeepTutor-native browser renders without a second round-trip.
"""
if query.strip():
response = self._get("/search", q=query.strip(), limit=limit)
else:
response = self._get("/skills", limit=limit, sort=sort)
try:
payload = response.json()
except ValueError as exc:
raise HubError(f"{self.name}: catalog returned invalid JSON") from exc
rows = payload if isinstance(payload, list) else None
if rows is None and isinstance(payload, dict):
for key in ("results", "skills", "items"):
if isinstance(payload.get(key), list):
rows = payload[key]
break
if rows is None:
raise HubError(f"{self.name}: unrecognised catalog response shape")
out: list[dict[str, Any]] = []
for row in rows:
if isinstance(row, dict):
listing = self._listing_from_row(row)
if listing is not None:
out.append(listing)
return out
def detail(self, slug: str) -> dict[str, Any]:
"""Full metadata + SKILL.md body for one hub skill (browser detail view)."""View on GitHub (pinned to 3e82f13042)
Solutions
- Check response status and Content-Type: curl -i $BASE_URL/skills
- Fix base_url in settings/skill_hubs.json to the actual API root
- If a proxy intercepts the call, add an exception or correct headers
- Retry transient gateway failures (502/503) before giving up
Defensive patterns
Strategy: try-catch
Validate before calling
resp = httpx.get(f"{BASE}/skills")
if "application/json" not in resp.headers.get("content-type", ""):
raise RuntimeError(f"non-JSON content-type: {resp.status_code}") Try / catch
try:
listings = hub.catalog()
except HubError as e:
if "invalid JSON" in str(e):
log.warning("hub returned non-JSON; possibly down: %s", e)
return []
raise Prevention
- Monitor hub Content-Type in integration tests
- Retry on 5xx before surfacing to users
When it happens
Trigger: Calling catalog() (used by hub_catalog and the tests test_clawhub_catalog_normalises_rows / test_clawhub_catalog_uses_search_when_queried) when /skills or /search responds with non-JSON such as a 502 HTML page, an empty body, or a text/plain message.
Common situations: Reverse proxy or CDN serving an error page; base_url misconfigured to a non-API host; hub behind auth wall returning an HTML login redirect; trailing-slash or path-join bug producing a 404 HTML page.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- {self.name}: unrecognised catalog response shape
- {self.name}: skill detail returned invalid JSON
- {self.name}: publish request failed: {exc}
- {self.name}: invalid JSON listing skills
- Channel onboarding provider request failed ({type(exc).__nam
AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27).
Data as JSON: /api/errors/7c082cdb1834a150.
Report an issue: GitHub.