conductor-oss/conductor · error · IllegalArgumentException
Skill package is missing SKILL.md
Error message
Skill package is missing SKILL.md
What it means
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).
Source
Thrown at agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/service/SkillRegistryService.java:483
}
contentByPath.put(path, content.toByteArray());
files.add(
SkillFileEntry.builder()
.path(path)
.size(size)
.sha256(hex(digest.digest()))
.contentType(contentType(path))
.build());
}
} catch (IllegalArgumentException e) {
throw e;
} catch (Exception e) {
throw new IllegalArgumentException("Invalid skill package zip: " + e.getMessage(), e);
}
byte[] skillMdBytes = contentByPath.get("SKILL.md");
if (skillMdBytes == null) {
throw new IllegalArgumentException("Skill package is missing SKILL.md");
}
files.sort(Comparator.comparing(SkillFileEntry::getPath));
String skillMd = decodeUtf8("SKILL.md", skillMdBytes);
Map<String, Object> frontmatter = parseSkillFrontmatter(skillMd);
String name = requiredString(frontmatter, "name");
validateSkillName(name);
String description = stringValue(frontmatter.get("description"));
if (description == null || description.isBlank()) {
description = stringValue(manifest.get("description"));
}
Map<String, String> agentFiles = new LinkedHashMap<>();
Map<String, Map<String, Object>> scripts = new LinkedHashMap<>();
List<String> resourceFiles = new ArrayList<>();
for (String path : contentByPath.keySet()) {
if (path.equals("SKILL.md")) {View on GitHub (pinned to cf7c3e4a8a)
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'.
Example fix
# before: zipped the folder, creating my-skill/SKILL.md zip -r skill.zip my-skill/ # after: zip from inside so SKILL.md is at root cd my-skill && zip -r ../skill.zip *
Defensive patterns
Strategy: validation
Validate before calling
// Before uploading, confirm SKILL.md is at the zip root
try (var z = new ZipFile(packageFile)) {
if (z.stream().noneMatch(e -> e.getName().equals("SKILL.md")))
throw new IllegalArgumentException("SKILL.md missing at zip root");
} Type guard
static boolean hasRootSkillMd(java.io.File zip) throws java.io.IOException {
try (var z = new ZipFile(zip)) {
return z.stream().anyMatch(e -> e.getName().equals("SKILL.md"));
}
} Try / catch
try { skillRegistryService.register(manifest, pkg); }
catch (IllegalArgumentException e) {
if (e.getMessage().contains("missing SKILL.md")) { /* rebuild zip with SKILL.md at root */ }
else throw e;
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Skill package contains duplicate path: {path}
- Invalid skill package zip: {e.getMessage()}
- Skill package exceeds max file count of {maxFileCount}
- Skill package contains oversized file: {path}
- Skill package contains duplicate script tool name: {toolName
AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14).
Data as JSON: /api/errors/4a17e5ccca45763c.
Report an issue: GitHub.