can1357/oh-my-pi · error · ToolError

Invalid skill:// URL path encoding: ${url}

Error message

Invalid skill:// URL path encoding: ${url}

What it means

The path portion of the skill:// URL failed percent-decoding. decodeURIComponent throws on malformed escapes (a bare % not followed by two hex digits, or truncated sequences like %E4%B8), and the function converts that into this ToolError. The URL must use valid percent-encoding for its path.

Source

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

	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);
	} catch (err) {
		const message = err instanceof Error ? err.message : String(err);
		throw new ToolError(message);
	}

	const 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 ToolError("Path traversal is not allowed in skill:// URLs");
	}
	// Agent Plugin skills (§4.1): the resource must canonically resolve within
	// the plugin root. Fail closed: a dangling or unresolvable path is rejected
	// rather than handed to bash, where writing through it could create the
	// outside target. Symlinks may target other files inside the same package.

View on GitHub (pinned to 9690622007)

Solutions

  1. Percent-encode the path properly: encode '%' as %25 and non-ASCII chars via encodeURIComponent.
  2. Remove stray bare % characters from the path segment.
  3. If the source was double-encoded, decode once at the producer instead of passing %%-style URLs.

Example fix

// before
resolveSkillUrlToPath("skill://my-skill/100%summary.md", skills);

// after
const p = encodeURIComponent("100%summary.md");
resolveSkillUrlToPath(`skill://my-skill/${p}`, skills);
Defensive patterns

Strategy: validation

Validate before calling

function isWellEncoded(s: string): boolean {
  try { decodeURIComponent(s); return true; } catch { return false; }
}
// check the raw path segment before calling the resolver

Try / catch

try {
  return resolveSkillUrlToPath(url, skills);
} catch (e) {
  if (e instanceof ToolError && e.message.startsWith("Invalid skill:// URL path encoding")) {
    // re-encode the path segment and retry once
  } else throw e;
}

Prevention

When it happens

Trigger: Calling resolveSkillUrlToPath with URLs such as "skill://name/100%done.md", "skill://name/%ZZ", or a path whose escape sequence is cut off. The error names the original URL.

Common situations: A model hand-writes percent signs in file names without encoding them (% as literal 'percent of'); double-encoding mangles sequences; a truncated URL from log/output clipping.

Related errors


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