conductor-oss/conductor · error · IllegalArgumentException

SKILL.md frontmatter must be a mapping

Error message

SKILL.md frontmatter must be a mapping

What it means

Thrown by SkillRegistryService when the YAML frontmatter of a SKILL.md file parses successfully but is not a key/value mapping (e.g. it is a bare list or scalar). The loader uses SnakeYAML's SafeConstructor, so a top-level YAML scalar like `hello` or a `- item` list yields a String/ArrayList rather than a Map, which this branch rejects. The check exists because the rest of the registry assumes structured metadata keyed by field name.

Solutions

  1. Open the offending SKILL.md and rewrite the frontmatter as a mapping: each field on its own line as `key: value`.
  2. Validate the YAML parses to an object before upload using `yq '. | type' SKILL.md` or a local SnakeYAML load.
  3. Ensure there is exactly one document and it starts with a key, not a `-`.

Example fix

// before (SKILL.md frontmatter)
---
- name: my-skill
  version: 1.0.0
---
// after
---
name: my-skill
version: 1.0.0
---
Defensive patterns

Strategy: validation

Validate before calling

// Validate SKILL.md frontmatter is a mapping before publishing.
private static void assertFrontmatterIsMap(String skillMd) {
    Matcher m = FRONTMATTER_PATTERN.matcher(skillMd);
    if (!m.matches()) throw new IllegalArgumentException("missing frontmatter");
    Object parsed = new Yaml(new SafeConstructor(new LoaderOptions())).load(m.group(1));
    if (parsed != null && !(parsed instanceof Map<?, ?>)) {
        throw new IllegalArgumentException(
            "frontmatter is " + parsed.getClass().getSimpleName() + ", must be a mapping");
    }
}

Type guard

boolean isFrontmatterMap(String skillMd) {
    Matcher m = FRONTMATTER_PATTERN.matcher(skillMd);
    if (!m.matches()) return false;
    Object v = new Yaml(new SafeConstructor(new LoaderOptions())).load(m.group(1));
    return v == null || v instanceof Map<?, ?>;
}

Try / catch

try {
    Map<String,Object> fm = skillRegistry.parseFrontmatter(skillMd);
} catch (IllegalArgumentException e) {
    // surface the message to the author; do not retry
    return badRequest(e.getMessage());
}

Prevention

When it happens

Trigger: Uploading/parsing a SKILL.md whose frontmatter between the `---` fences is a YAML list (`- name: x`) or a bare scalar (`just a string`). The FRONTMATTER_PATTERN matches, yaml.load() returns a non-Map, and the `instanceof Map<?,?>` guard fails.

Common situations: Author copy-pastes a list-style YAML config into frontmatter; a generator emits array metadata; frontmatter was hand-edited and demoted from `name: foo` to `- name: foo`.

Related errors


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

Appendix: source

Thrown at agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/service/SkillRegistryService.java:577

            out.write(buffer, 0, read);
        }
        return out.toByteArray();
    }

    private Map<String, Object> parseSkillFrontmatter(String skillMd) {
        Matcher matcher = FRONTMATTER_PATTERN.matcher(skillMd);
        if (!matcher.matches()) {
            throw new IllegalArgumentException("SKILL.md is missing required YAML frontmatter");
        }
        try {
            LoaderOptions options = new LoaderOptions();
            Yaml yaml = new Yaml(new SafeConstructor(options));
            Object value = yaml.load(matcher.group(1));
            if (value == null) {
                return Map.of();
            }
            if (!(value instanceof Map<?, ?> map)) {
                throw new IllegalArgumentException("SKILL.md frontmatter must be a mapping");
            }
            return MAPPER.convertValue(map, MAP_TYPE);
        } catch (IllegalArgumentException e) {
            throw e;
        } catch (Exception e) {
            throw new IllegalArgumentException(
                    "Invalid SKILL.md frontmatter: " + e.getMessage(), e);
        }
    }

    private String decodeUtf8(String path, byte[] data) {
        try {
            return StandardCharsets.UTF_8
                    .newDecoder()
                    .onMalformedInput(CodingErrorAction.REPORT)
                    .onUnmappableCharacter(CodingErrorAction.REPORT)
                    .decode(ByteBuffer.wrap(data))
                    .toString();

View on GitHub (pinned to cf7c3e4a8a)