HKUDS/DeepTutor · error · HubError
{self.name}: skill detail returned invalid JSON
Error message
{self.name}: skill detail returned invalid JSON What it means
Raised by HubProvider.detail when GET /skills/{slug} returns a body that fails JSON parsing. The body is HTML/plain-text/empty, so metadata and the SKILL.md body cannot be extracted.
Source
Thrown at deeptutor/services/skill/hub.py:430
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}
# 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()]View on GitHub (pinned to 3e82f13042)
Solutions
- curl -i $BASE_URL/skills/<slug> and inspect status/content-type
- Verify the slug exists via catalog/search first
- Correct base_url to the API host
- HTML-encode or validate the slug before calling detail
Defensive patterns
Strategy: try-catch
Validate before calling
resp = httpx.get(f"{BASE}/skills/{slug}")
ct = resp.headers.get("content-type", "")
if resp.status_code != 200 or "json" not in ct:
skip_detail(slug) # don't call detail() Try / catch
try:
d = hub.detail(slug)
except HubError as e:
if "invalid JSON" in str(e):
log.warning("detail unavailable for %s", slug); d = None
else: raise Prevention
- Validate slug format before calling
- Cache successful detail responses
When it happens
Trigger: Calling detail('slug') when the hub's skill-detail endpoint returns an HTML 404/500 page, an empty body, or a text error — e.g. a slug route not implemented by the deployed hub version.
Common situations: Slug-specific routes missing on older hub versions; proxy error pages; base_url pointing at the web UI rather than the API; slug containing characters that trigger a gateway rejection.
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}: catalog returned invalid JSON
- {self.name}: unrecognised skill detail shape
- {self.name}: invalid JSON listing skills
- {self.name}: unrecognised search response shape
- {self.name}: unrecognised catalog response shape
AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27).
Data as JSON: /api/errors/debca0ff61e61d7f.
Report an issue: GitHub.