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

  1. Check response status and Content-Type: curl -i $BASE_URL/skills
  2. Fix base_url in settings/skill_hubs.json to the actual API root
  3. If a proxy intercepts the call, add an exception or correct headers
  4. 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

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

Related errors


AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27). Data as JSON: /api/errors/7c082cdb1834a150. Report an issue: GitHub.