{"record":{"id":"ab2b95ce46ffced2","repo":"zylon-ai/private-gpt","slug":"missing-skill-md","errorCode":"MISSING_SKILL_MD","errorMessage":"SKILL.md is required","messagePattern":"SKILL\\.md is required","errorType":"exception","errorClass":"SkillDomainError","httpStatus":null,"severity":"error","filePath":"private_gpt/components/skills/services/skill_service.py","lineNumber":320,"sourceCode":"        self._cache.set(\n            \"skill-body\",\n            cache_key,\n            parsed.body,\n            ttl_seconds=_SKILL_BODY_CACHE_TTL_SECONDS,\n        )\n        return parsed.body\n\n    async def list_version_files(self, version: SkillVersionEntity) -> list[str]:\n        \"\"\"Relative paths of all files bundled in a skill version.\"\"\"\n        files = await self._storage_component.list_files(version.storage_prefix)\n        return sorted(files)\n\n\ndef _extract_skill_md(files: list[StoredFile]) -> str:\n    for file in files:\n        if file.path == \"SKILL.md\":\n            return file.content.decode(\"utf-8\")\n    raise SkillDomainError(SkillErrorCode.MISSING_SKILL_MD, \"SKILL.md is required\")\n","sourceCodeStart":302,"sourceCodeEnd":321,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/components/skills/services/skill_service.py#L302-L321","documentation":"_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.","triggerScenarios":"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.","commonSituations":"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.","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)`."],"exampleFix":"# before\n# zip built from parent dir -> entries: ['my-skill/SKILL.md', ...]\nawait service.create_skill(..., files=files)  # MISSING_SKILL_MD\n\n# after\n# zip built inside the skill dir -> entries: ['SKILL.md', ...]\nassert any(f.path == \"SKILL.md\" for f in files)\nawait service.create_skill(..., files=files)","handlingStrategy":"validation","validationCode":"def bundle_has_skill_md(files: list) -> bool:\n    return any(f.path == \"SKILL.md\" for f in files)\n\nassert bundle_has_skill_md(files), \"Bundle must contain SKILL.md at its root\"","typeGuard":"def is_valid_skill_bundle(files: list) -> bool:\n    return any(getattr(f, \"path\", None) == \"SKILL.md\" for f in files)","tryCatchPattern":"try:\n    await service.create_skill(..., files=files)\nexcept SkillDomainError as e:\n    if e.code is SkillErrorCode.MISSING_SKILL_MD:\n        files = normalize_bundle_paths(files)  # strip directory prefixes, fix casing\n        await service.create_skill(..., files=files)\n    else:\n        raise","preventionTips":["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."],"tags":["skills","packaging","manifest","upload","validation"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}