siyuan-note/siyuan · error
skill resource not found: %s/%s
Error message
skill resource not found: %s/%s
What it means
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.
Source
Thrown at kernel/util/skill.go:364
return nil
})
if err != nil {
truncated = true
}
sort.Strings(resources)
return resources, truncated
}
func readSkillResource(skillDir, skillName, resource string) (string, error) {
realRoot, err := filepath.EvalSymlinks(skillDir)
if err != nil {
return "", fmt.Errorf("skill not found: %s", skillName)
}
target := filepath.Join(realRoot, filepath.FromSlash(resource))
realTarget, err := filepath.EvalSymlinks(target)
if err != nil {
return "", fmt.Errorf("skill resource not found: %s/%s", skillName, resource)
}
if realTarget != realRoot && !gulu.File.IsSubPath(realRoot, realTarget) {
return "", fmt.Errorf("skill resource escapes skill directory: %s", resource)
}
info, err := os.Stat(realTarget)
if err != nil || !info.Mode().IsRegular() {
return "", fmt.Errorf("skill resource is not a regular file: %s/%s", skillName, resource)
}
if info.Size() > maxSkillResourceBytes {
return "", fmt.Errorf("skill resource exceeds the %d byte limit: %s/%s", maxSkillResourceBytes, skillName, resource)
}
file, err := os.Open(realTarget)
if err != nil {
return "", fmt.Errorf("skill resource not found: %s/%s", skillName, resource)
}
defer file.Close()View on GitHub (pinned to 8641553a1f)
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
Example fix
// before
LoadSkill("deploy/scripts/run.sh", enabled) // file is actually run-script.sh
// after
LoadSkill("deploy/scripts/run-script.sh", enabled) Defensive patterns
Strategy: validation
Validate before calling
const fs = require("fs");
const p = require("path");
function resourceExists(skillDir, res) {
return fs.existsSync(p.join(skillDir, res.replace(/\\/g, "/")));
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
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
- installed package not found
- skill not found: %s
- skill resource is not a regular file: %s/%s
- skill resource read failed: %s/%s
- skill already exists: %s
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/10c7784814a5d531.
Report an issue: GitHub.