HKUDS/DeepTutor · error · HubError

{self.name}: unrecognised catalog response shape

Error message

{self.name}: unrecognised catalog response shape

What it means

Raised by HubProvider.catalog when the parsed JSON payload is neither a top-level list nor a dict containing a 'results', 'skills', or 'items' list. The client refuses to guess rows and aborts instead of silently returning an empty catalog.

Source

Thrown at deeptutor/services/skill/hub.py:416

        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)."""
        try:
            payload = self._get(f"/skills/{slug}").json()
        except ValueError as exc:
            raise HubError(f"{self.name}: skill detail returned invalid JSON") from exc
        if not isinstance(payload, dict):
            raise HubError(f"{self.name}: unrecognised skill detail shape")
        skill = payload.get("skill") if isinstance(payload.get("skill"), dict) else payload
        listing = self._listing_from_row(skill) or {"slug": slug, "name": slug}

View on GitHub (pinned to 3e82f13042)

Solutions

  1. curl the endpoint and confirm the list lives under results/skills/items or at the top level
  2. Update the hub to emit one of the expected keys
  3. Pin or align hub API version with the client's expected envelope
  4. Fall back to CommandProvider for non-conforming hubs

Example fix

// before: {"payload": {"skills": [...]}}
// after:  {"skills": [...]}
Defensive patterns

Strategy: validation

Validate before calling

payload = httpx.get(f"{BASE}/skills").json()
rows = payload if isinstance(payload, list) else next((payload.get(k) for k in ("results","skills","items") if isinstance(payload, dict) and isinstance(payload.get(k), list)), None)
if rows is None: raise SystemExit('unsupported envelope')

Type guard

def extract_rows(p: object) -> list | None:
    if isinstance(p, list):
        return p
    if isinstance(p, dict):
        for k in ("results", "skills", "items"):
            if isinstance(p.get(k), list):
                return p[k]
    return None

Try / catch

try:
    hub.catalog()
except HubError as e:
    if "unrecognised catalog response shape" in str(e):
        return []
    raise

Prevention

When it happens

Trigger: Calling catalog() when the hub responds with a dict like {"error": "..."}, a scalar/null payload, or a nested envelope whose list lives deeper than one level.

Common situations: Hub API schema drift after an upgrade; hub returning an error envelope with HTTP 200; custom self-hosted hub with a different catalog envelope.

Related errors


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