siyuan-note/siyuan · error

skill resource is not a regular file: %s/%s

Error message

skill resource is not a regular file: %s/%s

What it means

readSkillResource only serves regular files: after stat, if the target is missing, unreadable, or not a regular file (directory, FIFO, device, socket), it returns this error. Directories within a skill must not be requested as resources.

Source

Thrown at kernel/util/skill.go:372

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()
	data, err := io.ReadAll(io.LimitReader(file, maxSkillResourceBytes+1))
	if err != nil {
		return "", fmt.Errorf("skill resource read failed: %s/%s", skillName, resource)
	}
	if len(data) > maxSkillResourceBytes {
		return "", fmt.Errorf("skill resource exceeds the %d byte limit: %s/%s", maxSkillResourceBytes, skillName, resource)
	}
	if !utf8.Valid(data) {

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Append the actual filename — resource locators must point at files, not directories
  2. List the skill directory to find the concrete file to request
  3. Remove or replace any non-regular files (FIFOs, sockets) inside the skill directory
  4. Fix permissions so the kernel can stat and read the file

Example fix

// before
LoadSkill("my-skill/assets", enabled) // assets is a directory
// after
LoadSkill("my-skill/assets/diagram.svg", enabled)
Defensive patterns

Strategy: validation

Validate before calling

const stat = require("fs").statSync(p.join(skillDir, resource));
if (!stat.isFile()) throw new Error("resource must be a regular file");

Prevention

When it happens

Trigger: Calling LoadSkill with 'skill/dirname' where dirname is a directory; requesting a FIFO/device/socket path inside the skill; stat failure due to permissions on the resolved target.

Common situations: Agent guesses a directory path instead of a file path; requesting 'assets/' style paths; special files accidentally created inside a skill directory by other tooling.

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/770b28aab9b1f59c. Report an issue: GitHub.