HKUDS/DeepTutor · error · HubError
{self.name}: unrecognised skill detail shape
Error message
{self.name}: unrecognised skill detail shape What it means
Raised by HubProvider.detail when the parsed JSON for /skills/{slug} is not a dict (e.g. a list or a string). The detail view needs an object with at least a nested 'skill' object or flat skill fields.
Source
Thrown at deeptutor/services/skill/hub.py:432
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}
# Owner sometimes lives at the envelope top level, not inside ``skill``.
if not listing.get("owner") and isinstance(payload.get("owner"), dict):
owner = payload["owner"]
listing["owner"] = str(owner.get("displayName") or owner.get("handle") or "")
listing["owner_url"] = str(owner.get("htmlUrl") or "")
dist = payload.get("distTags") if isinstance(payload.get("distTags"), dict) else {}
listing["version"] = str(dist.get("latest") or listing.get("version") or "")
listing["content"] = str(skill.get("description") or "")
# EduHub's ``tags`` field is a dist-tags map; the topical labels live in
# ``keywords``. Fall back to a list-shaped ``tags`` for other hubs.
keywords = skill.get("keywords")
if not isinstance(keywords, list):
keywords = skill.get("tags") if isinstance(skill.get("tags"), list) else []
listing["tags"] = [str(t) for t in keywords if str(t).strip()]
return listing
View on GitHub (pinned to 3e82f13042)
Solutions
- Confirm the endpoint returns an object: {"skill": {...}, "body": "...", ...}
- Search for the correct slug via hub.search() before calling detail
- Align hub API version with the client contract
Example fix
// before: [{"slug": "x", ...}]
// after: {"skill": {"slug": "x", ...}, "body": "# ..."} Defensive patterns
Strategy: type-guard
Validate before calling
p = httpx.get(f"{BASE}/skills/{slug}").json()
if not isinstance(p, dict): skip_detail(slug) Type guard
def is_detail_dict(p: object) -> bool:
return isinstance(p, dict) Try / catch
try:
hub.detail(slug)
except HubError as e:
if "unrecognised skill detail shape" in str(e):
return {"slug": slug, "name": slug} # minimal fallback
raise Prevention
- Confirm slugs exist via search() before fetching detail
When it happens
Trigger: Calling detail(slug) when the hub returns a JSON array (e.g. a list of matches) or a scalar for a slug endpoint.
Common situations: Hub version that returns list-shaped search results for unknown slugs instead of an object; misrouted endpoint returning collection payloads.
Related errors
- {self.name}: unrecognised search response shape
- {self.name}: unrecognised catalog response shape
- {self.name}: skill detail returned invalid JSON
- {self.name}: catalog returned invalid JSON
- {self.name}: publish request failed: {exc}
AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27).
Data as JSON: /api/errors/bd1a6480f685c3d0.
Report an issue: GitHub.