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
- Percent-encode the path properly: encode '%' as %25 and non-ASCII chars via encodeURIComponent.
- Remove stray bare % characters from the path segment.
- 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
- Always encodeURIComponent each path segment; never hand-place % in URLs.
- Escape literal '%' as %25.
- Avoid passing URLs through log/clipboard round-trips that may truncate them.
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
- Invalid skill:// URL: ${url}
- skill:// URL requires a skill name: ${url}
- invalid utf-8 sequence
- invalid byte sequence: {:02x?}
- Discord webhook credential is not a valid URL
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/cfbe094a1e76da5a.
Report an issue: GitHub.