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

  1. Ensure SKILL.md exists at the zip root: unzip -l skill.zip should list 'SKILL.md' as a top-level entry.
  2. 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 ...'.
  3. 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

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


AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14). Data as JSON: /api/errors/4a17e5ccca45763c. Report an issue: GitHub.