odysseus-dev/odysseus · error · HTTPException

Skill source unavailable

Error message

Skill source unavailable

What it means

404 raised when skills_manager.read_skill_md(name, owner) returns None: the skill is in the invocable index but its SKILL.md file could not be read from disk. The route has already confirmed the skill exists (821 passed), so this is specifically a missing-or-unreadable source file, not a missing skill.

Source

Thrown at routes/skills_routes.py:1343

        user = _owner(request)
        try:
            body = await request.json()
        except Exception:
            body = {}
        request_text = (body.get("request") or "").strip() if isinstance(body, dict) else ""

        invokable = {
            s.get("name"): s for s in skills_manager.index_for(owner=user)
            if (s.get("name") or "").strip()
        }
        match = invokable.get(skill_id)
        if not match:
            raise HTTPException(404, "Skill is not available for slash invocation")

        name = match.get("name")
        md = skills_manager.read_skill_md(name, owner=user)
        if md is None:
            raise HTTPException(404, "Skill source unavailable")

        skills_manager.record_use(name, owner=user)
        message = (
            "Apply the skill below to my request, following its Procedure / Pitfalls / Verification.\n\n"
            f"--- BEGIN SKILL ---\n{md}\n--- END SKILL ---\n\n"
            + (f"Request: {request_text}" if request_text else "Request: (use the skill as appropriate)")
        )
        return {
            "ok": True,
            "type": "skill",
            "name": name,
            "command": f"/{name}",
            "message": message,
        }

    @router.get("/{skill_id}")
    async def get_skill(request: Request, skill_id: str):
        user = _owner(request)

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Check the skill's directory on disk: does SKILL.md exist and is it readable by the server process?
  2. If the file is genuinely gone, re-import the skill or re-create it via POST /add, then re-save metadata.
  3. If files exist but under a different directory name (rename/orphan case), restore the directory to match the stored skill name or fix the entry to point at the real name.
  4. Restart the service if the index is cached in memory so it re-scans disk state.

Example fix

# before
resp = client.post(f"/api/skills/{name}/invoke", json={})
assert resp.status_code == 200

# after
resp = client.post(f"/api/skills/{name}/invoke", json={})
if resp.status_code == 404 and "source unavailable" in resp.text:
    # index entry exists but SKILL.md is missing on disk — re-import or re-add
    skill_md = rebuild_or_fetch_skill_md(name)
    client.post(f"/api/skills/{name}/markdown", json={"markdown": skill_md})
    resp = client.post(f"/api/skills/{name}/invoke", json={})
Defensive patterns

Strategy: validation

Validate before calling

# probe source availability via the markdown route before invoking
r = client.get(f"{base}/api/skills/{name}/markdown")
if r.status_code == 404:
    raise FileNotFoundError(f"SKILL.md missing for '{name}' — index entry is orphaned")

Type guard

def has_source(name: str, skills_manager, owner: str) -> bool:
    """Skill is invokable only if its markdown source is readable."""
    return skills_manager.read_skill_md(name, owner=owner) is not None

Prevention

When it happens

Trigger: POST /{skill_id}/invoke for a skill whose index entry survived but whose SKILL.md was deleted, moved, or left unreadable on disk — e.g. the directory was partially deleted, a save was interrupted, the file lives under a renamed directory (the orphaned-id rename bug shape referenced by #1333), or file permissions changed after the index was built.

Common situations: Skills directory edited by hand or by another tool while the server was running; a crashed import/save leaving an index entry without its markdown; skills migrated between machines with an index/cache copied but files skipped; containerized deployments where the volume mount dropped files.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/e0569c077a075976. Report an issue: GitHub.