{"record":{"id":"b502af2fe6d4f8bb","repo":"can1357/oh-my-pi","slug":"file-not-found-resolvedpath","errorCode":null,"errorMessage":"File not found: ${resolvedPath}","messagePattern":"File not found: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/internal-urls/skill-protocol.ts","lineNumber":89,"sourceCode":"\t\t\tconst relativePath = decodeURIComponent(urlPath.slice(1));\n\t\t\tvalidateRelativePath(relativePath);\n\t\t\ttargetPath = path.join(skill.baseDir, relativePath);\n\n\t\t\tconst resolvedPath = path.resolve(targetPath);\n\t\t\tconst resolvedBaseDir = path.resolve(skill.baseDir);\n\t\t\tif (!resolvedPath.startsWith(resolvedBaseDir + path.sep) && resolvedPath !== resolvedBaseDir) {\n\t\t\t\tthrow new Error(\"Path traversal is not allowed\");\n\t\t\t}\n\t\t\t// Agent Plugin skills (§4.1): the resource must canonically resolve\n\t\t\t// within the plugin root; a dangling or unresolvable path fails closed.\n\t\t\t// Symlinks may target other files inside the same package.\n\t\t\tif (skill.containRoot) {\n\t\t\t\tconst contained = await resolveContainedPath(skill.containRoot, resolvedPath);\n\t\t\t\tif (contained.status === \"outside\") {\n\t\t\t\t\tthrow new Error(`skill:// path resolves outside the plugin root: ${url.href}`);\n\t\t\t\t}\n\t\t\t\tif (contained.status === \"missing\") {\n\t\t\t\t\tthrow new Error(`File not found: ${resolvedPath}`);\n\t\t\t\t}\n\t\t\t\ttargetPath = contained.realPath;\n\t\t\t}\n\t\t} else {\n\t\t\ttargetPath = context?.pathOnly === true ? skill.baseDir : skill.filePath;\n\t\t}\n\n\t\tlet stats: fsTypes.Stats;\n\t\ttry {\n\t\t\tstats = await fs.stat(targetPath);\n\t\t} catch (error) {\n\t\t\tif (isEnoent(error)) {\n\t\t\t\tthrow new Error(`File not found: ${targetPath}`);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\n\t\tif (stats.isDirectory()) {","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/internal-urls/skill-protocol.ts#L71-L107","documentation":"This error comes from the skill:// internal-URL protocol handler. When a skill belongs to an Agent Plugin with a containment root (`skill.containRoot`), the requested path is canonically resolved inside that root via `resolveContainedPath`. If the resolution reports `missing`, meaning the path (after resolving symlinks) does not exist within the plugin root, the handler fails closed with 'File not found'.","triggerScenarios":"Calling `SkillProtocolHandler.resolve()` on a `skill://<name>/<path>` URL where the skill has a `containRoot` and `resolveContainedPath(skill.containRoot, resolvedPath)` returns status `\"missing\"` — i.e. the referenced relative path does not exist (or is an unresolvable/dangling symlink) inside the plugin root.","commonSituations":"Referencing an auxiliary file (script, template, data file) alongside a plugin skill's SKILL.md that was renamed, moved, or never shipped; typos in the path portion of skill:// URLs; dangling symlinks inside the plugin package; tooling or an agent hallucinating a path that exists in a different plugin version.","solutions":["Verify the path in the skill:// URL exists inside the plugin root (run `ls` on the plugin's baseDir/containRoot).","Check the plugin version — the file may have been renamed or removed; update the URL or the plugin.","If the file is a symlink, confirm its target resolves inside the plugin root and actually exists.","Read the SKILL.md itself (skill://<name> with no path) to discover the correct bundled file names."],"exampleFix":"// before\nconst res = await handler.resolve(parseInternalUrl(\"skill://deploy/scripts/run.sh\"));\n// after\nconst res = await handler.resolve(parseInternalUrl(\"skill://deploy/scripts/run-remote.sh\")); // path corrected to match bundled file","handlingStrategy":"validation","validationCode":"import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\n// Before resolving, confirm the relative path exists under the skill's baseDir.\nconst rel = \"scripts/run-remote.sh\";\nconst abs = path.resolve(skill.baseDir, rel);\ntry {\n  await fs.stat(abs); // throws ENOENT if missing\n} catch {\n  throw new Error(`Refusing skill:// URL: ${rel} does not exist in skill ${skill.name}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  const resource = await handler.resolve(url);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"File not found:\")) {\n    // fall back to reading the SKILL.md index\n    return handler.resolve(parseInternalUrl(`skill://${skillName}`));\n  }\n  throw err;\n}","preventionTips":["List the plugin root's real file names before building skill://<name>/<path> URLs.","Read skill://<name> (SKILL.md) first to discover bundled resource paths.","Re-sync/reinstall plugins after version changes so referenced files exist.","Never hand-write paths from memory — verify against the plugin directory."],"tags":["filesystem","skill-protocol","plugin","file-not-found"],"backgroundTag":"file-not-found","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}