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

  1. Include the skill name as the URL host: skill://my-skill or skill://my-skill/sub/path
  2. Check the variable holding the skill name for undefined/empty before constructing the URL
  3. 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

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


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