{"record":{"id":"37e1c7ac7b8d57be","repo":"can1357/oh-my-pi","slug":"managed-skill-name-needs-a-non-empty-descript","errorCode":null,"errorMessage":"Managed skill \"${name}\" needs a non-empty description.","messagePattern":"Managed skill \"(.+?)\" needs a non-empty description\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/autolearn/managed-skills.ts","lineNumber":160,"sourceCode":"\t\treturn await fs.open(file, UPDATE_FILE_OPEN_FLAGS);\n\t} catch (err) {\n\t\tif ((err as { code?: string }).code === \"ELOOP\") {\n\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","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/autolearn/managed-skills.ts#L142-L178","documentation":"writeManagedSkill rejects a skill whose description sanitizes to an empty string. Managed descriptions must be non-empty because the discovery scan (requireDescription) silently drops skills without descriptions — the tool would otherwise report success for a skill that never appears.","triggerScenarios":"Calling writeManagedSkill with an empty/whitespace description, or a description made only of characters stripped by sanitizeManagedDescription (control chars, angle brackets, backticks/~~~ fences).","commonSituations":"Auto-learn generates a description consisting entirely of markup like `<system>` tags or code fences, which sanitization removes; the caller passes an empty description field.","solutions":["Provide a plain-text, one-line description with meaningful content","Remove angle brackets, control characters, and backtick/tilde fences from the generated description before writing","Check the sanitized result (sanitizeManagedDescription) is non-empty before calling"],"exampleFix":"// before\nwriteManagedSkill({ name: \"foo\", description: \"<skills>...\", body });\n// after\nwriteManagedSkill({ name: \"foo\", description: \"Repairs failing unit tests in the build pipeline\", body });","handlingStrategy":"validation","validationCode":"const desc = sanitizeManagedDescription(input.description);\nif (!desc) throw new Error(\"description is empty after sanitization\");","typeGuard":null,"tryCatchPattern":"try {\n  await writeManagedSkill(input);\n} catch (err) {\n  if (String((err as Error).message).includes(\"non-empty description\")) {\n    // regenerate or supply a plain-text description, then retry\n  } else throw err;\n}","preventionTips":["Always pass a plain-text, one-line description with real content","Strip angle brackets, control chars, and code fences from generated descriptions before writing","Never leave the description field empty or whitespace-only in auto-learn output"],"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"}