zylon-ai/private-gpt · error · SkillDomainError
MISSING_SKILL_MD
MISSING_SKILL_MD
Error message
SKILL.md is required
What it means
_extract_skill_md scans the stored files of a skill bundle for an entry whose path is exactly 'SKILL.md' and decodes it; when no file with that exact relative path exists, it raises SkillDomainError MISSING_SKILL_MD. Every valid skill bundle must contain SKILL.md at its root — it is the manifest that carries name/description frontmatter and instructions.
Source
Thrown at private_gpt/components/skills/services/skill_service.py:320
self._cache.set(
"skill-body",
cache_key,
parsed.body,
ttl_seconds=_SKILL_BODY_CACHE_TTL_SECONDS,
)
return parsed.body
async def list_version_files(self, version: SkillVersionEntity) -> list[str]:
"""Relative paths of all files bundled in a skill version."""
files = await self._storage_component.list_files(version.storage_prefix)
return sorted(files)
def _extract_skill_md(files: list[StoredFile]) -> str:
for file in files:
if file.path == "SKILL.md":
return file.content.decode("utf-8")
raise SkillDomainError(SkillErrorCode.MISSING_SKILL_MD, "SKILL.md is required")
View on GitHub (pinned to 4a030776a3)
Solutions
- Ensure the bundle contains SKILL.md at the root, exact casing, no directory prefix.
- When zipping, archive from inside the skill directory so stored paths are relative ('SKILL.md', not 'my-skill/SKILL.md').
- If your storage normalizes case, check for 'skill.md' and rename before packaging.
- Validate the bundle client-side before upload: `any(f.path == 'SKILL.md' for f in files)`.
Example fix
# before # zip built from parent dir -> entries: ['my-skill/SKILL.md', ...] await service.create_skill(..., files=files) # MISSING_SKILL_MD # after # zip built inside the skill dir -> entries: ['SKILL.md', ...] assert any(f.path == "SKILL.md" for f in files) await service.create_skill(..., files=files)
Defensive patterns
Strategy: validation
Validate before calling
def bundle_has_skill_md(files: list) -> bool:
return any(f.path == "SKILL.md" for f in files)
assert bundle_has_skill_md(files), "Bundle must contain SKILL.md at its root" Type guard
def is_valid_skill_bundle(files: list) -> bool:
return any(getattr(f, "path", None) == "SKILL.md" for f in files) Try / catch
try:
await service.create_skill(..., files=files)
except SkillDomainError as e:
if e.code is SkillErrorCode.MISSING_SKILL_MD:
files = normalize_bundle_paths(files) # strip directory prefixes, fix casing
await service.create_skill(..., files=files)
else:
raise Prevention
- Zip skill bundles from inside the skill directory so SKILL.md lands at the root.
- Validate for an exact 'SKILL.md' path (case-sensitive) before upload.
- Use a packaging script/scaffold that always emits SKILL.md first.
When it happens
Trigger: Uploading/activating a bundle where no file has path == 'SKILL.md' — e.g. it was renamed (skill.md, SKILL.MD), nested in a subdirectory ('my-skill/SKILL.md'), dropped during packaging, or the zip was created from the parent directory so all paths are prefixed.
Common situations: Zipping the parent folder instead of its contents (paths like 'my-skill/SKILL.md'); case mismatches on case-sensitive stores; build scripts that exclude the manifest; editing a bundle by hand and forgetting to re-add the file.
Related errors
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/ab2b95ce46ffced2.
Report an issue: GitHub.