can1357/oh-my-pi · error · ToolError
Invalid skill:// URL
Error message
Invalid skill:// URL: ${url} What it means
resolveSkillUrlToPath parses skill:// URLs with a strict regex (^skill://name/path[?query][#fragment]). If the string does not match that shape at all, the function throws this ToolError instead of guessing. This keeps malformed internal URLs from reaching the filesystem layer.
Solutions
- Format the URL exactly as skill://<skill-name>[/<path>][?#fragment].
- Check the string starts with "skill://" before calling; route other schemes to their own resolvers.
- Encode the skill name and path segments (encodeURIComponent) so no regex-breaking characters leak in.
Example fix
// before
resolveSkillUrlToPath("skills/my-skill/SKILL.md", skills);
// after
resolveSkillUrlToPath("skill://my-skill/SKILL.md", skills); Defensive patterns
Strategy: validation
Validate before calling
if (!/^skill:\/\/[^/?#]+(\/[^?#]*)?([?#].*)?$/.test(url)) {
throw new Error(`not a well-formed skill:// URL: ${url}`);
} Type guard
function isSkillUrl(u: string): boolean {
return u.startsWith("skill://") && /^skill:\/\/[^/?#]+/.test(u);
} Try / catch
try {
return resolveSkillUrlToPath(url, skills);
} catch (e) {
if (e instanceof ToolError && e.message.startsWith("Invalid skill:// URL")) {
// fall back to another resolver or report the malformed URL to the caller
} else throw e;
} Prevention
- Build URLs with a helper instead of string concatenation.
- Route by scheme prefix before calling resolvers (skill:// vs file:// vs https://).
- Percent-encode name and path segments when constructing the URL.
When it happens
Trigger: Calling resolveSkillUrlToPath (or resolveInternalUrlToPath / resolvedPath / resolved) with a URL lacking the skill:// scheme, e.g. "skill:/name", "https://skill/name", "skill://", or a URL with unescaped whitespace/newlines that break the regex.
Common situations: A model hallucinates a different URL scheme in a bash command; config stores a hand-typed skill URL with a typo; a caller passes a plain file path where a skill URL was expected.
Related errors
- skill:// URL requires a skill name
- returned an invalid image URL
- Discord attachment URL is invalid
- Invalid memory glob URL
- Invalid skill:// URL path encoding
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/2e5983563d0a2574.
Report an issue: GitHub.
Appendix: source
Thrown at packages/coding-agent/src/tools/bash-skill-urls.ts:56
export interface InternalUrlExpansionOptions {
skills: readonly Skill[];
attachments?: readonly ImageAttachmentEntry[];
noEscape?: boolean;
internalRouter?: InternalUrlResolver;
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) {View on GitHub (pinned to 9690622007)