can1357/oh-my-pi · error
Managed skill "${name}" needs a non-empty description.
Error message
Managed skill "${name}" needs a non-empty description. What it means
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.
Source
Thrown at packages/coding-agent/src/autolearn/managed-skills.ts:160
return await fs.open(file, UPDATE_FILE_OPEN_FLAGS);
} catch (err) {
if ((err as { code?: string }).code === "ELOOP") {
throw new Error(`Managed skill "${name}" SKILL.md is a symlink; refusing to overwrite it.`);
}
throw err;
}
}
/** Create or update a managed `SKILL.md`. Returns the resolved file path. */
export async function writeManagedSkill(input: WriteManagedSkillInput): Promise<{ path: string }> {
const name = sanitizeSkillName(input.name);
const description = sanitizeManagedDescription(input.description);
const body = input.body.trim();
// Reject empty content: an all-whitespace/control description sanitizes to ""
// and the `requireDescription` discovery scan then silently drops the skill,
// so the tool would report success for a skill that never appears.
if (!description) {
throw new Error(`Managed skill "${name}" needs a non-empty description.`);
}
if (!body) {
throw new Error(`Managed skill "${name}" needs a non-empty body.`);
}
const content = `${toSkillFrontmatter(name, description)}\n${body}\n`;
// Cap the UTF-8 byte size of the FINAL file (body + description + frontmatter),
// not the UTF-16 code-unit length of the body alone.
const bytes = Buffer.byteLength(content, "utf8");
if (bytes > MAX_MANAGED_SKILL_BYTES) {
throw new Error(
`Managed skill is ${bytes} bytes; the limit is ${MAX_MANAGED_SKILL_BYTES}. Trim the body or description.`,
);
}
return serializeSkillMutation(name, async () => {
await assertManagedRootSafe();
const dir = path.join(getManagedSkillsDir(), name);
const file = path.join(dir, "SKILL.md");
// Reject a symlinked skill directory: an intermediate symlink would let theView on GitHub (pinned to 9690622007)
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
Example fix
// before
writeManagedSkill({ name: "foo", description: "<skills>...", body });
// after
writeManagedSkill({ name: "foo", description: "Repairs failing unit tests in the build pipeline", body }); Defensive patterns
Strategy: validation
Validate before calling
const desc = sanitizeManagedDescription(input.description);
if (!desc) throw new Error("description is empty after sanitization"); Try / catch
try {
await writeManagedSkill(input);
} catch (err) {
if (String((err as Error).message).includes("non-empty description")) {
// regenerate or supply a plain-text description, then retry
} else throw err;
} Prevention
- 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
When it happens
Trigger: Calling writeManagedSkill with an empty/whitespace description, or a description made only of characters stripped by sanitizeManagedDescription (control chars, angle brackets, backticks/~~~ fences).
Common situations: 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.
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
Related errors
- Managed skill "${name}" needs a non-empty body.
- Report cannot be empty.
- Search scope entries must be non-empty paths or globs
- Empty report. ${reportIssueDeviceUsage()}
- Message must not be empty.
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/37e1c7ac7b8d57be.
Report an issue: GitHub.