{"record":{"id":"10c7784814a5d531","repo":"siyuan-note/siyuan","slug":"skill-resource-not-found-s-s","errorCode":null,"errorMessage":"skill resource not found: %s/%s","messagePattern":"skill resource not found: (.+?)/(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill.go","lineNumber":364,"sourceCode":"\t\treturn nil\n\t})\n\tif err != nil {\n\t\ttruncated = true\n\t}\n\tsort.Strings(resources)\n\treturn resources, truncated\n}\n\nfunc readSkillResource(skillDir, skillName, resource string) (string, error) {\n\trealRoot, err := filepath.EvalSymlinks(skillDir)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"skill not found: %s\", skillName)\n\t}\n\n\ttarget := filepath.Join(realRoot, filepath.FromSlash(resource))\n\trealTarget, err := filepath.EvalSymlinks(target)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"skill resource not found: %s/%s\", skillName, resource)\n\t}\n\tif realTarget != realRoot && !gulu.File.IsSubPath(realRoot, realTarget) {\n\t\treturn \"\", fmt.Errorf(\"skill resource escapes skill directory: %s\", resource)\n\t}\n\n\tinfo, err := os.Stat(realTarget)\n\tif err != nil || !info.Mode().IsRegular() {\n\t\treturn \"\", fmt.Errorf(\"skill resource is not a regular file: %s/%s\", skillName, resource)\n\t}\n\tif info.Size() > maxSkillResourceBytes {\n\t\treturn \"\", fmt.Errorf(\"skill resource exceeds the %d byte limit: %s/%s\", maxSkillResourceBytes, skillName, resource)\n\t}\n\n\tfile, err := os.Open(realTarget)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"skill resource not found: %s/%s\", skillName, resource)\n\t}\n\tdefer file.Close()","sourceCodeStart":346,"sourceCodeEnd":382,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/util/skill.go#L346-L382","documentation":"After resolving the requested resource path with EvalSymlinks, readSkillResource checks that the real target still lies inside the skill's real root (gulu.File.IsSubPath). If not, the resource is reported as not found rather than read, because the path left the skill directory. This message means the resource path (or a symlink along it) does not resolve to anything inside the skill.","triggerScenarios":"Requesting 'name/resource' where the resource file does not exist under the skill directory (EvalSymlinks ENOENT); a symlink inside the skill points outside and the target itself is missing.","commonSituations":"Typo or wrong case in the resource filename; requesting a resource that was moved/renamed in a newer skill version; broken symlink inside the skill bundle.","solutions":["Check the exact resource path against the files listed in the skill frontmatter/body","Fix filename case and typos (filesystems are case-sensitive on Linux)","Remove or repair broken symlinks inside the skill directory","Re-load the skill body (LoadSkill without a resource) to see the current resource list"],"exampleFix":"// before\nLoadSkill(\"deploy/scripts/run.sh\", enabled) // file is actually run-script.sh\n// after\nLoadSkill(\"deploy/scripts/run-script.sh\", enabled)","handlingStrategy":"validation","validationCode":"const fs = require(\"fs\");\nconst p = require(\"path\");\nfunction resourceExists(skillDir, res) {\n  return fs.existsSync(p.join(skillDir, res.replace(/\\\\/g, \"/\")));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Verify resource paths against the file list embedded in the skill content","Watch filename case on case-sensitive filesystems","Repair or remove broken symlinks inside skill bundles"],"tags":["skills","not-found","filesystem"],"backgroundTag":"file-not-found","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}