can1357/oh-my-pi · error · ToolError

skill:// URL requires a skill name: ${url}

Error message

skill:// URL requires a skill name: ${url}

What it means

Although the regex matched, the captured skill-name segment was empty. The function requires at least a skill name after skill:// and throws when it is absent. In practice this guards a defensive branch on the URL grammar.

Source

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

	localOptions?: LocalProtocolOptions;
	cwd?: string;
	sessionFile?: string;
	ensureLocalParentDirs?: boolean;
}

/**
 * Resolve a single skill:// URL to its absolute filesystem path.
 * Does NOT read file content or verify existence.
 */
export function resolveSkillUrlToPath(url: string, skills: readonly Skill[]): string {
	const parsed = /^skill:\/\/([^/?#]+)(\/[^?#]*)?(?:[?#].*)?$/.exec(url);
	if (!parsed) {
		throw new ToolError(`Invalid skill:// URL: ${url}`);
	}

	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}`);
	}

View on GitHub (pinned to 9690622007)

Solutions

  1. Include a valid skill name in the URL: skill://my-skill/path.
  2. Verify the skill-name variable is non-empty before interpolating it into the URL.
  3. List available skills first (see the skills registry) and pick an existing name.

Example fix

// before
const url = `skill://${skillName ?? ""}/SKILL.md`;

// after
if (!skillName) throw new Error("skill name required");
const url = `skill://${skillName}/SKILL.md`;
Defensive patterns

Strategy: validation

Validate before calling

const m = /^skill:\/\/([^/?#]+)/.exec(url);
if (!m || !m[1]) throw new Error(`skill URL missing name: ${url}`);

Type guard

function hasSkillName(url: string): boolean {
  const m = /^skill:\/\/([^/?#]+)/.exec(url);
  return !!m && m[1].length > 0;
}

Try / catch

try {
  return resolveSkillUrlToPath(url, skills);
} catch (e) {
  if (e instanceof ToolError && e.message.includes("requires a skill name")) {
    // reconstruct the URL with an explicit skill name
  } else throw e;
}

Prevention

When it happens

Trigger: A URL like "skill://" where the regex's ([^/?#]+) matched via an alternate path producing an empty first segment — practically seen when callers build the URL by concatenating an empty skill-name variable.

Common situations: Template/codegen producing `skill://${name}/...` where `name` is empty; an upstream parser stripping the name as a namespace prefix.

Related errors


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