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

  1. Check the exact resource path against the files listed in the skill frontmatter/body
  2. Fix filename case and typos (filesystems are case-sensitive on Linux)
  3. Remove or repair broken symlinks inside the skill directory
  4. 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

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


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/10c7784814a5d531. Report an issue: GitHub.