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
- Add valid YAML frontmatter with `name` and `description` fields that pass validateSkillFrontmatter
- Make frontmatter.name exactly equal command.name and frontmatter.description exactly equal command.description.trim()
- Ensure the markdown body after frontmatter is nonempty
- 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
- Generate SKILL.md markdown from a template that emits frontmatter from name/description
- Trim descriptions once and reuse the same string in frontmatter and command
- Never pass a slug different from name
- Assert the body is nonempty in fixture builders
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
- Explicit skill input must reference a unique assigned…
- fixture_state_invalid
- A full lowercase source SHA is required.
- A reusable lease cannot be replaced and reacquired in the…
- A reusable lease handoff requires an execution workspace…
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)