paperclipai/paperclip · error · CapabilityMockControlPlaneError

invalid_skill_document

invalid_skill_document

Error message

Provide a complete SKILL.md with name and description matching the command inputs, a nonempty body, and slug equal to name when supplied

What it means

CapabilityMockControlPlaneError with code `invalid_skill_document`, thrown when a create_skill command's markdown does not form a valid SKILL.md. The adapter requires YAML frontmatter that passes validateSkillFrontmatter, frontmatter name equal to command.name, description equal to the trimmed command.description, a nonempty body, and — if slug is supplied — slug equal to name. This guards fixture/mock skill creation against half-written documents.

Solutions

  1. Add valid YAML frontmatter with `name` and `description` fields that pass validateSkillFrontmatter
  2. Make frontmatter.name exactly equal command.name and frontmatter.description exactly equal command.description.trim()
  3. Ensure the markdown body after frontmatter is nonempty
  4. Omit `slug` (it defaults to name) or set it exactly equal to command.name

Example fix

// before
createSkill({ name: "my-skill", description: "Does things", markdown: "Just a body", slug: "other" })
// after
const markdown = "---\nname: my-skill\ndescription: Does things\n---\n\nDoes things when needed.";
createSkill({ name: "my-skill", description: "Does things", markdown }) // no slug, or slug: "my-skill"
Defensive patterns

Strategy: validation

Validate before calling

const doc = parseFrontmatterMarkdown(markdown);
const meta = validateSkillFrontmatter(doc.frontmatter);
const valid = doc.hasFrontmatter && !!meta && doc.body.trim().length > 0
  && doc.frontmatter.name === name
  && doc.frontmatter.description === description.trim()
  && (slug === undefined || slug === name);
if (!valid) throw new Error("invalid_skill_document");

Type guard

function isValidSkillDoc(doc, cmd) {
  return doc.hasFrontmatter
    && typeof doc.frontmatter?.name === "string"
    && doc.frontmatter.name === cmd.name
    && doc.frontmatter.description === cmd.description.trim()
    && doc.body.trim() !== ""
    && (cmd.slug === undefined || cmd.slug === cmd.name);
}

Try / catch

try {
  await adapter.applyCommand(createSkillCmd);
} catch (e) {
  if (e instanceof CapabilityMockControlPlaneError && e.code === "invalid_skill_document") {
    // fix markdown frontmatter/body and retry
  } else throw e;
}

Prevention

When it happens

Trigger: applyCommand → #executeCommand for a create_skill command where: markdown has no frontmatter; frontmatter fails validateSkillFrontmatter (missing name/description or bad format); body is whitespace-only; frontmatter.name differs from command.name; frontmatter.description differs from command.description.trim(); or command.slug is set and differs from command.name.

Common situations: Fixture authors writing SKILL.md markdown without the --- frontmatter block; editing the description in the command but not in the frontmatter; supplying a slug that is not the slugified name; empty or placeholder body text.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/f38b80c6997e4534. Report an issue: GitHub.

Appendix: source

Thrown at packages/paperclip-runner/src/mock-core/capability-mock-control-plane-adapter.ts:721

    const entityRefs = [`task:${task.id}`];
    const scheduledWakeIds: string[] = [];
    switch (command.kind) {
      case "report_progress": {
        requireText(command.body, "progress body");
        const comment = this.#appendComment(task.id, run.actorId, command.body);
        entityRefs.push(`comment:${comment.id}`);
        break;
      }
      case "create_skill": {
        requireText(command.name, "skill name");
        requireText(command.markdown, "skill markdown");
        const document = parseFrontmatterMarkdown(command.markdown);
        const validMetadata = validateSkillFrontmatter(document.frontmatter);
        if (!document.hasFrontmatter || !validMetadata || !document.body.trim()
          || document.frontmatter.name !== command.name
          || document.frontmatter.description !== command.description.trim()
          || (command.slug !== undefined && command.slug !== command.name)) {
          throw new CapabilityMockControlPlaneError(
            "invalid_skill_document",
            "Provide a complete SKILL.md with name and description matching the command inputs, a nonempty body, and slug equal to name when supplied",
          );
        }
        const slug = command.slug ?? command.name;
        const skills = this.#state.skills ??= [];
        if (skills.some(skill => skill.companyId === run.companyId && skill.slug === slug)) {
          throw new CapabilityMockControlPlaneError("fixture_state_invalid", "A skill with that name already exists");
        }
        const skill = { id: this.#id("skill"), companyId: run.companyId, name: command.name,
          slug, description: command.description.trim(), markdown: command.markdown, versionId: this.#id("skill-version") };
        skills.push(skill);
        entityRefs.push(`skill:${skill.id}`);
        break;
      }
      case "write_document": {
        requireText(command.key, "document key");
        requireText(command.title, "document title");

View on GitHub (pinned to 3f1d897a7c)