HKUDS/DeepTutor · error · HubError

{self.name}: unrecognised search response shape

Error message

{self.name}: unrecognised search response shape

What it means

Raised by HubProvider.search when the hub's /search endpoint returns a payload that is neither a JSON list nor a dict containing a 'results', 'items', or 'skills' list. The client cannot locate any rows to normalise into HubSkillRef objects, so it aborts rather than returning misleading empty results.

Source

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

            raise HubError(
                f"{self.name}: HTTP {response.status_code} for {path}: {response.text[:200]}"
            )
        return response

    def search(self, query: str, *, limit: int = 10) -> list[HubSkillRef]:
        response = self._get("/search", q=query, limit=limit)
        try:
            payload = response.json()
        except ValueError as exc:
            raise HubError(f"{self.name}: search 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", "items", "skills"):
                if isinstance(payload.get(key), list):
                    rows = payload[key]
                    break
        if rows is None:
            raise HubError(f"{self.name}: unrecognised search response shape")
        refs: list[HubSkillRef] = []
        for row in rows:
            if not isinstance(row, dict):
                continue
            slug = str(row.get("slug") or "").strip()
            if not slug:
                continue
            refs.append(
                HubSkillRef(
                    hub=self.name,
                    slug=slug,
                    display_name=str(row.get("displayName") or row.get("name") or slug),
                    summary=str(row.get("summary") or row.get("description") or ""),
                    version=str(row.get("version") or ""),
                )
            )
        return refs

View on GitHub (pinned to 3e82f13042)

Solutions

  1. Inspect the raw response: curl $BASE_URL/search?q=test and check the envelope key holding the list
  2. If the list sits under a different key, ensure the hub returns one of results/items/skills at the top level (or a bare array)
  3. Verify base_url in settings/skill_hubs.json points at a compatible hub API version
  4. If the hub genuinely cannot conform, wrap it with a CommandProvider instead

Example fix

// settings/skill_hubs.json (hub must return one of these shapes)
// before: {"data": {"results": [...]}}
// after:  {"results": [...]}
Defensive patterns

Strategy: validation

Validate before calling

import httpx
resp = httpx.get(f"{BASE}/search", params={"q": q})
payload = resp.json()
assert isinstance(payload, list) or any(isinstance(payload.get(k), list) for k in ("results","items","skills") if isinstance(payload, dict)), "envelope not supported"

Type guard

def has_supported_rows(p: object) -> bool:
    if isinstance(p, list):
        return True
    return isinstance(p, dict) and any(isinstance(p.get(k), list) for k in ("results", "items", "skills"))

Try / catch

try:
    refs = hub.search(q)
except HubError as e:
    if "unrecognised search response shape" in str(e):
        return []  # degrade gracefully
    raise

Prevention

When it happens

Trigger: Calling hub.search(query) (or `skill search`) against a ClawHub-like provider whose /search response is a bare dict like {"error": ...}, a scalar, or an envelope using an unexpected key (e.g. {'data': {'hits': [...]}} with double nesting).

Common situations: Hub API version change that altered the response envelope; pointing base_url at a different service or an HTML error page that parsed as a scalar; a proxy returning {"detail": "..."} on auth failure.

Related errors


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