can1357/oh-my-pi · error

Invalid skill name "${raw}". Use lowercase letters, digits,

Error message

Invalid skill name "${raw}". Use lowercase letters, digits, and hyphens (1-64 chars, starting with a letter or digit).

What it means

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).

Source

Thrown at packages/coding-agent/src/autolearn/managed-skills.ts:37

/** Hard cap on a managed SKILL.md body to keep generated skills bounded. */
export const MAX_MANAGED_SKILL_BYTES = 64_000;

const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;

/** Resolve the isolated managed-skills directory (`~/.omp/agent/managed-skills`). */
export function getManagedSkillsDir(agentDir: string = getAgentDir()): string {
	return path.join(agentDir, "managed-skills");
}

/**
 * Validate + normalize a managed-skill name. Throws on anything outside the
 * strict allowlist so a bad name can never escape `getManagedSkillsDir()`
 * (blocks `..`, slashes, empty, and uppercase).
 */
export function sanitizeSkillName(raw: string): string {
	const name = raw.trim().toLowerCase();
	if (!SKILL_NAME_PATTERN.test(name)) {
		throw new Error(
			`Invalid skill name "${raw}". Use lowercase letters, digits, and hyphens (1-64 chars, starting with a letter or digit).`,
		);
	}
	return name;
}

/**
 * Whether `name` is a safe managed-skill name (the exact post-sanitize shape).
 * Used to validate names read from disk at discovery time — a managed
 * `SKILL.md` whose `frontmatter.name` was not produced by `sanitizeSkillName`
 * (e.g. hand-placed) must not render unescaped into the system prompt.
 */
export function isValidManagedSkillName(name: string): boolean {
	return SKILL_NAME_PATTERN.test(name);
}

/**
 * Neutralize a machine-generated managed-skill description so it cannot break

View on GitHub (pinned to 9690622007)

Solutions

  1. Slugify the input before calling: trim, lowercase, replace non [a-z0-9] runs with '-', trim leading/trailing hyphens, cap at 64 chars
  2. Validate the name with isValidManagedSkillName before calling the API
  3. Ensure the name starts with a letter or digit (a leading '-' is invalid)
  4. Check the failing name in the message for hidden characters like slashes or dots

Example fix

// before
writeManagedSkill({ name: taskTitle, ... });
// after
const name = taskTitle.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 64) || 'skill';
writeManagedSkill({ name, ... });
Defensive patterns

Strategy: validation

Validate before calling

const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
if (!SKILL_NAME_PATTERN.test(raw.trim().toLowerCase())) {
  throw new Error(`invalid skill name: ${raw}`);
}

Type guard

function isValidManagedSkillName(name: string): boolean {
  return /^[a-z0-9][a-z0-9-]{0,63}$/.test(name);
}

Try / catch

let name: string;
try {
  name = sanitizeSkillName(raw);
} catch {
  name = slugify(raw); // lowercase, [^a-z0-9]+ -> '-', trim '-', slice(0,64)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/3edef6d93e9cabab. Report an issue: GitHub.