{"record":{"id":"51c4290f17db8147","repo":"can1357/oh-my-pi","slug":"managed-skill-name-needs-a-non-empty-body","errorCode":null,"errorMessage":"Managed skill \"${name}\" needs a non-empty body.","messagePattern":"Managed skill \"(.+?)\" needs a non-empty body\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/autolearn/managed-skills.ts","lineNumber":163,"sourceCode":"\t\t\tthrow new Error(`Managed skill \"${name}\" SKILL.md is a symlink; refusing to overwrite it.`);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\n/** Create or update a managed `SKILL.md`. Returns the resolved file path. */\nexport async function writeManagedSkill(input: WriteManagedSkillInput): Promise<{ path: string }> {\n\tconst name = sanitizeSkillName(input.name);\n\tconst description = sanitizeManagedDescription(input.description);\n\tconst body = input.body.trim();\n\t// Reject empty content: an all-whitespace/control description sanitizes to \"\"\n\t// and the `requireDescription` discovery scan then silently drops the skill,\n\t// so the tool would report success for a skill that never appears.\n\tif (!description) {\n\t\tthrow new Error(`Managed skill \"${name}\" needs a non-empty description.`);\n\t}\n\tif (!body) {\n\t\tthrow new Error(`Managed skill \"${name}\" needs a non-empty body.`);\n\t}\n\tconst content = `${toSkillFrontmatter(name, description)}\\n${body}\\n`;\n\t// Cap the UTF-8 byte size of the FINAL file (body + description + frontmatter),\n\t// not the UTF-16 code-unit length of the body alone.\n\tconst bytes = Buffer.byteLength(content, \"utf8\");\n\tif (bytes > MAX_MANAGED_SKILL_BYTES) {\n\t\tthrow new Error(\n\t\t\t`Managed skill is ${bytes} bytes; the limit is ${MAX_MANAGED_SKILL_BYTES}. Trim the body or description.`,\n\t\t);\n\t}\n\treturn serializeSkillMutation(name, async () => {\n\t\tawait assertManagedRootSafe();\n\t\tconst dir = path.join(getManagedSkillsDir(), name);\n\t\tconst file = path.join(dir, \"SKILL.md\");\n\t\t// Reject a symlinked skill directory: an intermediate symlink would let the\n\t\t// write escape the isolated managed root. lstat does not follow the final\n\t\t// component, so a symlinked `dir` is caught here.\n\t\tconst dirStat = await fs.lstat(dir).catch(err => {","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/autolearn/managed-skills.ts#L145-L181","documentation":"writeManagedSkill rejects a skill body that is empty after trimming. A managed skill without body content is useless and would produce an empty SKILL.md document.","triggerScenarios":"Calling writeManagedSkill with body set to \"\", whitespace-only, or a string that trims to nothing.","commonSituations":"The generating agent produced frontmatter but no content; a template or upstream stream failed and left the body blank; the caller passed the wrong variable (e.g. description into body).","solutions":["Supply the actual markdown body content for the skill","Check the upstream generation step that produced the body for empty output","Swap arguments if body and description were passed in the wrong order"],"exampleFix":"// before\nwriteManagedSkill({ name: \"foo\", description: \"d\", body: \"   \" });\n// after\nwriteManagedSkill({ name: \"foo\", description: \"d\", body: \"# Fix flaky tests\\n\\n1. Run the suite twice...\" });","handlingStrategy":"validation","validationCode":"if (!input.body || !input.body.trim()) throw new Error(\"skill body is empty\");","typeGuard":null,"tryCatchPattern":"try {\n  await writeManagedSkill(input);\n} catch (err) {\n  if (String((err as Error).message).includes(\"non-empty body\")) {\n    // regenerate content or abort the skill creation\n  } else throw err;\n}","preventionTips":["Verify the generation step produced non-empty markdown before writing","Check argument order — don't pass an empty string where body is expected","Treat empty model output as a generation failure, not a valid skill"],"tags":["validation","empty-input"],"backgroundTag":"missing-required-field","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}