can1357/oh-my-pi · error
skill:// URL requires a skill name: skill://<name>
Error message
skill:// URL requires a skill name: skill://<name>
What it means
The skill:// protocol resolves a URL's host component to a skill name. If the URL has neither a rawHost nor a hostname (e.g. just 'skill://' or 'skill:///path'), there is no skill to look up, so resolve() throws immediately with the expected URL form in the message. The scheme requires the shape skill://<name> or skill://<name>/<path>.
Source
Thrown at packages/coding-agent/src/internal-urls/skill-protocol.ts:56
normalized.includes("/..")
) {
throw new Error("Path traversal (..) is not allowed in skill:// URLs");
}
}
/**
* 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);
View on GitHub (pinned to 9690622007)
Solutions
- Include the skill name as the URL host: skill://my-skill or skill://my-skill/sub/path
- Check the variable holding the skill name for undefined/empty before constructing the URL
- List available skills (e.g. via complete() or getActiveSkills()) and pick a valid name
Example fix
// before
const url = `skill://${skillName ?? ''}/notes.md`;
// after
if (!skillName) throw new Error('skillName is required');
const url = `skill://${skillName}/notes.md`; Defensive patterns
Strategy: validation
Validate before calling
const m = /^skill:\/\/([^/]+)/.exec(url);
if (!m || !m[1]) {
throw new Error(`skill URL needs a host skill name: ${url}`);
}
const skillName = m[1]; Type guard
function hasSkillHost(url: { rawHost?: string; hostname: string }): boolean {
return Boolean(url.rawHost || url.hostname);
} Try / catch
try {
return await handler.resolve(url, ctx);
} catch (err) {
if (err instanceof Error && err.message.includes('requires a skill name')) {
// reconstruct the URL with a valid skill host
}
throw err;
} Prevention
- Always template skill:// URLs as skill://${name} or skill://${name}/${path}
- Assert the name variable is non-empty before interpolation
- Never build skill:// URLs from naive string splitting that can drop the host
When it happens
Trigger: resolve() called with an InternalUrl whose rawHost and hostname are both empty: 'skill://', 'skill:///folder/file.md', or a URL where parsing dropped the host (e.g. 'skill:/name' malformed input).
Common situations: Building the URL from a template with an undefined/empty skill variable; stripping the host during string manipulation; hand-written URLs omitting the skill name and jumping straight to a path.
Related errors
- Provider delete URL must not embed an account credential
- ${destination} returned an invalid upload URL
- Destination option endpoint must be an absolute URL
- Destination option endpoint must use HTTP or HTTPS
- Destination option ${optionName} must be an absolute HTTP UR
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/8449692e734ecf32.
Report an issue: GitHub.