can1357/oh-my-pi · error · ToolError

Unknown skill: ${rawSkillSegment}. Available: ${availableStr

Error message

Unknown skill: ${rawSkillSegment}. Available: ${availableStr}

What it means

The skill name inside the skill:// URL did not match any registered skill. matchSkillName compares the (decoded, suffix-stripped) segment against known Skill names; on no match the error lists every available name so the caller can self-correct. Namespaced names like "plugin:skill" and colon line-range suffixes like ":1-5" are handled during matching.

Source

Thrown at packages/coding-agent/src/tools/bash-skill-urls.ts:77

	let rawSkillSegment = parsed[1];
	if (!rawSkillSegment) {
		throw new ToolError(`skill:// URL requires a skill name: ${url}`);
	}
	// Decode percent-encoded colons (%3A) used for namespaced skill names
	try {
		rawSkillSegment = decodeURIComponent(rawSkillSegment);
	} catch {
		// Leave as-is if decoding fails
	}

	// Resolve skill name by longest-prefix match against registered skills.
	// This handles namespaced skills ("plugin:skill") where the URI may also
	// carry a colon-delimited suffix (e.g., ":1-5" line range).
	const { skill, suffix } = matchSkillName(rawSkillSegment, skills);
	if (!skill) {
		const available = skills.map(s => s.name);
		const availableStr = available.length > 0 ? available.join(", ") : "none";
		throw new ToolError(`Unknown skill: ${rawSkillSegment}. Available: ${availableStr}`);
	}

	// Combine any colon suffix (line range like ":1-5") with the path segment
	const rawPath = (parsed[2] ?? "") + (suffix ? `/${suffix}` : "");
	const hasRelativePath = rawPath !== "" && rawPath !== "/";

	if (!hasRelativePath) {
		return path.resolve(skill.baseDir);
	}

	let relativePath: string;
	try {
		relativePath = decodeURIComponent(rawPath.slice(1));
	} catch {
		throw new ToolError(`Invalid skill:// URL path encoding: ${url}`);
	}
	try {
		validateRelativePath(relativePath);

View on GitHub (pinned to 9690622007)

Solutions

  1. Use exactly one of the names listed in the error's "Available: ..." list.
  2. Confirm the skill/plugin is actually loaded and enabled before referencing it.
  3. If namespaced, include the full "plugin:skill" name (URL-encode the colon as %3A if needed).
  4. Re-fetch the current skill list — the registry may have changed since the URL was built.

Example fix

// before
resolveSkillUrlToPath("skill://code-review/SKILL.md", skills);
// Unknown skill: code-review. Available: plugin:review

// after
resolveSkillUrlToPath("skill://plugin%3Areview/SKILL.md", skills);
Defensive patterns

Strategy: validation

Validate before calling

const name = decodeURIComponentSafe(nameSegment);
if (!skills.some(s => s.name === name)) {
  throw new Error(`unknown skill '${name}'; available: ${skills.map(s => s.name).join(", ")}`);
}

Try / catch

try {
  return resolveSkillUrlToPath(url, skills);
} catch (e) {
  if (e instanceof ToolError && e.message.startsWith("Unknown skill")) {
    // parse 'Available:' list, pick closest match or report options to the user
  } else throw e;
}

Prevention

When it happens

Trigger: Calling resolveSkillUrlToPath("skill://typo-name/SKILL.md", skills) where "typo-name" matches no skill in the passed Skill[] — name misspelled, skill not loaded, wrong namespace prefix (missing "plugin:"), or a stale name after a plugin rename.

Common situations: A model invents a skill name; the user renamed or uninstalled a plugin skill; the skills array passed to the resolver is empty or filtered so a legitimately loaded skill looks unknown; colon-in-name edge cases where the suffix stripper eats part of the name.

Related errors


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