can1357/oh-my-pi · error
Unknown skill: ${skillName} Available: ${availableStr}
Error message
Unknown skill: ${skillName}
Available: ${availableStr} What it means
After extracting the skill name from the URL host, resolve() searches the active skill registry (context.skills or getActiveSkills()) for a matching name. If no skill with that exact name exists, the error lists all currently available skill names (or 'none' if the registry is empty) so the caller can correct the URL. Names are matched exactly — no fuzzy or case-insensitive fallback.
Source
Thrown at packages/coding-agent/src/internal-urls/skill-protocol.ts:63
* Handler for skill:// URLs.
*/
export class SkillProtocolHandler implements ProtocolHandler {
readonly scheme = "skill";
readonly immutable = true;
async resolve(url: InternalUrl, context?: ResolveContext): Promise<InternalResource> {
const skills = context?.skills ?? getActiveSkills();
const skillName = url.rawHost || url.hostname;
if (!skillName) {
throw new Error("skill:// URL requires a skill name: skill://<name>");
}
const skill = skills.find(s => s.name === skillName);
if (!skill) {
const available = skills.map(s => s.name);
const availableStr = available.length > 0 ? available.join(", ") : "none";
throw new Error(`Unknown skill: ${skillName}\nAvailable: ${availableStr}`);
}
let targetPath: string;
const urlPath = url.pathname;
const hasRelativePath = urlPath && urlPath !== "/" && urlPath !== "";
if (hasRelativePath) {
const relativePath = decodeURIComponent(urlPath.slice(1));
validateRelativePath(relativePath);
targetPath = path.join(skill.baseDir, relativePath);
const resolvedPath = path.resolve(targetPath);
const resolvedBaseDir = path.resolve(skill.baseDir);
if (!resolvedPath.startsWith(resolvedBaseDir + path.sep) && resolvedPath !== resolvedBaseDir) {
throw new Error("Path traversal is not allowed");
}
// Agent Plugin skills (§4.1): the resource must canonically resolve
// within the plugin root; a dangling or unresolvable path fails closed.View on GitHub (pinned to 9690622007)
Solutions
- Use one of the names listed in the error's 'Available:' line
- Check exact spelling and casing — matching is case-sensitive and exact
- Verify the skill's plugin/extension is installed and loaded (check discovery logs)
- Call getActiveSkills() (or the completion API) to enumerate valid names before resolving
- If the skill was renamed, update the stored URL
Example fix
// before
resolve('skill://Code-Review/README.md')
// after
resolve('skill://code-review/README.md') // exact registered name Defensive patterns
Strategy: fallback
Validate before calling
import { getActiveSkills } from '../extensibility/skills';
const names = new Set(getActiveSkills().map(s => s.name));
if (!names.has(skillName)) {
throw new Error(`Skill '${skillName}' not loaded. Available: ${[...names].join(', ') || 'none'}`);
} Type guard
function skillExists(name: string, skills: { name: string }[]): skills is [{ name: string }, ...{ name: string }[]] {
return skills.some(s => s.name === name);
} Try / catch
try {
return await handler.resolve(url, ctx);
} catch (err) {
if (err instanceof Error && err.message.startsWith('Unknown skill:')) {
const available = err.message.split('Available: ')[1] ?? '';
// pick closest match or surface the available list to the user
}
throw err;
} Prevention
- Copy skill names from the 'Available:' list or getActiveSkills(), never from memory
- Remember name matching is exact and case-sensitive
- Verify the owning plugin/extension is installed and loaded before linking
- After renaming a skill, update all persisted skill:// URLs
When it happens
Trigger: resolve('skill://typo-name/...') where 'typo-name' is not in the active skills list; resolving a skill that was removed, renamed, or not yet loaded; resolving in a context where no skills are registered (empty registry).
Common situations: Typos or wrong casing in the skill name (matching is exact); referencing a skill from a plugin that failed to load or was disabled; stale links to renamed skills; running in an environment (test, SDK embed) where skills were never discovered.
Related errors
- Not found: ${target}\nAvailable: ${availableStr}
- skill:// URL requires a skill name: skill://<name>
- Custom URL is required for option 3
- Custom URL is required for option 3
- Provider delete URL must not embed an account credential
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/1aa1930d41d6057e.
Report an issue: GitHub.