can1357/oh-my-pi · error · ToolError

skill:// path does not exist: ${url}

Error message

skill:// path does not exist: ${url}

What it means

For containRoot skills, resolveContainedPathSync must be able to resolve the target's real path; a "missing" status (dangling symlink or nonexistent file) throws this ToolError rather than handing a broken path to bash. This is the fail-closed counterpart of the containment check: unresolvable paths are rejected.

Source

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

	}

	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.
	if (skill.containRoot) {
		const contained = resolveContainedPathSync(skill.containRoot, resolvedPath);
		if (contained.status === "outside") {
			throw new ToolError(`skill:// path resolves outside the plugin root: ${url}`);
		}
		if (contained.status === "missing") {
			throw new ToolError(`skill:// path does not exist: ${url}`);
		}
		return contained.realPath;
	}

	return resolvedPath;
}

/**
 * Match a raw skill segment against registered skills using longest-prefix match.
 * Handles colons in both skill names (namespacing) and suffixes (line ranges).
 *
 * For "superpowers:brainstorming:1-5" with skill "superpowers:brainstorming":
 *   -> skill = superpowers:brainstorming, suffix = "1-5"
 * For "brainstorming" with skill "brainstorming":
 *   -> skill = brainstorming, suffix = undefined
 */
function matchSkillName(
	rawSegment: string,

View on GitHub (pinned to 9690622007)

Solutions

  1. List the skill directory to find the correct file name, then retry with the exact name.
  2. Check case sensitivity — file names must match exactly.
  3. If a symlink is dangling, restore its target or remove the link from the package.
  4. Reinstall/update the plugin if its files are missing from the installed copy.

Example fix

// before
resolveSkillUrlToPath("skill://my-skill/reference.md", skills);
// skill:// path does not exist

// after
resolveSkillUrlToPath("skill://my-skill/references/index.md", skills);
Defensive patterns

Strategy: fallback

Validate before calling

import * as fs from "node:fs";
const target = /* baseDir + decoded relative path */;
try { fs.realpathSync(target); } catch { throw new Error(`skill file missing: ${target}`); }

Try / catch

try {
  return resolveSkillUrlToPath(url, skills);
} catch (e) {
  if (e instanceof ToolError && e.message.includes("does not exist")) {
    // fall back: list the skill directory and retry with an existing file
  } else throw e;
}

Prevention

When it happens

Trigger: A skill:// URL like "skill://name/docs/missing.md" for a containRoot skill where the file does not exist, or where the final path component is a symlink whose target was deleted (dangling link).

Common situations: The model guesses a filename inside the skill that isn't there; the skill's docs changed between versions; a symlink target was removed after packaging; case-sensitivity mismatch (README.md vs readme.md) on case-sensitive filesystems.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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