{"record":{"id":"3edef6d93e9cabab","repo":"can1357/oh-my-pi","slug":"invalid-skill-name-raw-use-lowercase-letters","errorCode":null,"errorMessage":"Invalid skill name \"${raw}\". Use lowercase letters, digits, and hyphens (1-64 chars, starting with a letter or digit).","messagePattern":"Invalid skill name \"(.+?)\"\\. Use lowercase letters, digits, and hyphens \\(1-64 chars, starting with a letter or digit\\)\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/autolearn/managed-skills.ts","lineNumber":37,"sourceCode":"/** Hard cap on a managed SKILL.md body to keep generated skills bounded. */\nexport const MAX_MANAGED_SKILL_BYTES = 64_000;\n\nconst SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;\n\n/** Resolve the isolated managed-skills directory (`~/.omp/agent/managed-skills`). */\nexport function getManagedSkillsDir(agentDir: string = getAgentDir()): string {\n\treturn path.join(agentDir, \"managed-skills\");\n}\n\n/**\n * Validate + normalize a managed-skill name. Throws on anything outside the\n * strict allowlist so a bad name can never escape `getManagedSkillsDir()`\n * (blocks `..`, slashes, empty, and uppercase).\n */\nexport function sanitizeSkillName(raw: string): string {\n\tconst name = raw.trim().toLowerCase();\n\tif (!SKILL_NAME_PATTERN.test(name)) {\n\t\tthrow new Error(\n\t\t\t`Invalid skill name \"${raw}\". Use lowercase letters, digits, and hyphens (1-64 chars, starting with a letter or digit).`,\n\t\t);\n\t}\n\treturn name;\n}\n\n/**\n * Whether `name` is a safe managed-skill name (the exact post-sanitize shape).\n * Used to validate names read from disk at discovery time — a managed\n * `SKILL.md` whose `frontmatter.name` was not produced by `sanitizeSkillName`\n * (e.g. hand-placed) must not render unescaped into the system prompt.\n */\nexport function isValidManagedSkillName(name: string): boolean {\n\treturn SKILL_NAME_PATTERN.test(name);\n}\n\n/**\n * Neutralize a machine-generated managed-skill description so it cannot break","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/autolearn/managed-skills.ts#L19-L55","documentation":"sanitizeSkillName validates a managed-skill name against ^[a-z0-9][a-z0-9-]{0,63}$ after trimming and lowercasing. Names outside this strict allowlist are rejected because a bad name could escape getManagedSkillsDir() (path traversal via .. or slashes).","triggerScenarios":"Calling writeManagedSkill or any API that resolves a skill name (name/safe getters) with a raw string containing uppercase, slashes, '..', spaces, symbols, an empty string, or more than 64 characters that still fails the pattern after trim/lowercase.","commonSituations":"Auto-learn generates a skill title with spaces or punctuation; a user-facing label (e.g. 'Fix Build Errors!') is passed as the name instead of a slug; a name containing a path segment from user input.","solutions":["Slugify the input before calling: trim, lowercase, replace non [a-z0-9] runs with '-', trim leading/trailing hyphens, cap at 64 chars","Validate the name with isValidManagedSkillName before calling the API","Ensure the name starts with a letter or digit (a leading '-' is invalid)","Check the failing name in the message for hidden characters like slashes or dots"],"exampleFix":"// before\nwriteManagedSkill({ name: taskTitle, ... });\n// after\nconst name = taskTitle.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 64) || 'skill';\nwriteManagedSkill({ name, ... });","handlingStrategy":"validation","validationCode":"const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;\nif (!SKILL_NAME_PATTERN.test(raw.trim().toLowerCase())) {\n  throw new Error(`invalid skill name: ${raw}`);\n}","typeGuard":"function isValidManagedSkillName(name: string): boolean {\n  return /^[a-z0-9][a-z0-9-]{0,63}$/.test(name);\n}","tryCatchPattern":"let name: string;\ntry {\n  name = sanitizeSkillName(raw);\n} catch {\n  name = slugify(raw); // lowercase, [^a-z0-9]+ -> '-', trim '-', slice(0,64)\n}","preventionTips":["Slugify any user- or LLM-derived title before using it as a skill name","Validate with isValidManagedSkillName before calling write/delete APIs","Never pass path segments, spaces, or punctuation as skill names","Cap names at 64 characters and ensure they start with a letter or digit"],"tags":["validation","naming","path-safety"],"backgroundTag":"invalid-identifier-name","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}