{"record":{"id":"4a17e5ccca45763c","repo":"conductor-oss/conductor","slug":"skill-package-is-missing-skill-md","errorCode":null,"errorMessage":"Skill package is missing SKILL.md","messagePattern":"Skill package is missing SKILL\\.md","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/service/SkillRegistryService.java","lineNumber":483,"sourceCode":"                }\n                contentByPath.put(path, content.toByteArray());\n                files.add(\n                        SkillFileEntry.builder()\n                                .path(path)\n                                .size(size)\n                                .sha256(hex(digest.digest()))\n                                .contentType(contentType(path))\n                                .build());\n            }\n        } catch (IllegalArgumentException e) {\n            throw e;\n        } catch (Exception e) {\n            throw new IllegalArgumentException(\"Invalid skill package zip: \" + e.getMessage(), e);\n        }\n\n        byte[] skillMdBytes = contentByPath.get(\"SKILL.md\");\n        if (skillMdBytes == null) {\n            throw new IllegalArgumentException(\"Skill package is missing SKILL.md\");\n        }\n        files.sort(Comparator.comparing(SkillFileEntry::getPath));\n\n        String skillMd = decodeUtf8(\"SKILL.md\", skillMdBytes);\n        Map<String, Object> frontmatter = parseSkillFrontmatter(skillMd);\n        String name = requiredString(frontmatter, \"name\");\n        validateSkillName(name);\n        String description = stringValue(frontmatter.get(\"description\"));\n        if (description == null || description.isBlank()) {\n            description = stringValue(manifest.get(\"description\"));\n        }\n\n        Map<String, String> agentFiles = new LinkedHashMap<>();\n        Map<String, Map<String, Object>> scripts = new LinkedHashMap<>();\n        List<String> resourceFiles = new ArrayList<>();\n\n        for (String path : contentByPath.keySet()) {\n            if (path.equals(\"SKILL.md\")) {","sourceCodeStart":465,"sourceCodeEnd":501,"githubUrl":"https://github.com/conductor-oss/conductor/blob/cf7c3e4a8adfb158be778ab1ec525323c363cd3a/agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/service/SkillRegistryService.java#L465-L501","documentation":"Thrown by parseSkillPackage after the zip is fully parsed when no top-level 'SKILL.md' entry exists in the package. SKILL.md is the mandatory skill manifest file — its YAML frontmatter provides the name, description, and parameters. The lookup uses the normalized path 'SKILL.md', so the file must sit at the zip root (not in a subfolder).","triggerScenarios":"POST /api/skills/register with a zip whose SKILL.md is nested under a subdirectory (e.g. my-skill/SKILL.md), or is missing entirely, or is named skill.md (wrong case on case-sensitive stores).","commonSituations":"Zipping a parent folder instead of its contents (creating my-skill/ as the root); renaming SKILL.md; case mismatch from a case-insensitive dev environment surviving into a case-sensitive zip; a packaging script that omits SKILL.md.","solutions":["Ensure SKILL.md exists at the zip root: unzip -l skill.zip should list 'SKILL.md' as a top-level entry.","Zip the folder contents, not the folder: run zip from inside the skill directory (zip -r skill.zip * with SKILL.md present), or use 'zip skill.zip SKILL.md ...'.","Use the exact casing 'SKILL.md'."],"exampleFix":"# before: zipped the folder, creating my-skill/SKILL.md\nzip -r skill.zip my-skill/\n# after: zip from inside so SKILL.md is at root\ncd my-skill && zip -r ../skill.zip *","handlingStrategy":"validation","validationCode":"// Before uploading, confirm SKILL.md is at the zip root\ntry (var z = new ZipFile(packageFile)) {\n    if (z.stream().noneMatch(e -> e.getName().equals(\"SKILL.md\")))\n        throw new IllegalArgumentException(\"SKILL.md missing at zip root\");\n}","typeGuard":"static boolean hasRootSkillMd(java.io.File zip) throws java.io.IOException {\n    try (var z = new ZipFile(zip)) {\n        return z.stream().anyMatch(e -> e.getName().equals(\"SKILL.md\"));\n    }\n}","tryCatchPattern":"try { skillRegistryService.register(manifest, pkg); }\ncatch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"missing SKILL.md\")) { /* rebuild zip with SKILL.md at root */ }\n    else throw e;\n}","preventionTips":["Zip from inside the skill directory so files sit at the root, not under a parent folder.","Always include SKILL.md with exact casing in the packaging allowlist."],"tags":["java","conductor","skill-registry","package-integrity","zip","validation"],"backgroundTag":null,"analyzedSha":"cf7c3e4a8adfb158be778ab1ec525323c363cd3a","analyzedAt":"2026-08-14T03:33:19.897Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}