siyuan-note/siyuan · error

install skill failed

Error message

install skill %s failed: %s

What it means

installSkillDirs performs an overwrite-style install: it removes the existing destination directory and copies the extracted skill directory with filelock.Copy. If the copy fails, the error is wrapped as 'install skill <name> failed: <cause>'. The old directory has already been removed at this point, so a failed install may leave the skill missing until a retry succeeds.

Solutions

  1. Read the wrapped cause: 'no space left on device' means free disk space; 'permission denied' means fix ownership/permissions of the workspace skills directory
  2. Close other SiYuan instances/processes that may lock the skills directory, then retry
  3. Ensure the workspace is writable by the user running the kernel
  4. Retry the install — because the old directory was removed first, re-running restores the skill
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: skills dir must be writable and have headroom
const dir = getSkillsDir()
await fs.promises.access(dir, fs.constants.W_OK).catch(() => { throw new Error("skills dir not writable") })
const stat = await fs.promises.statfs(dir)
if (stat.bavail * stat.bsize < 50 * 1024 * 1024) throw new Error("low disk space")

Try / catch

try {
  await installSkill(src)
} catch (e) {
  if (String(e).startsWith("install skill") && String(e).includes("failed:")) {
    const cause = String(e).split("failed: ")[1]
    if (/no space left/i.test(cause)) freeDiskSpaceAndRetry()
    else if (/permission denied/i.test(cause)) fixWorkspacePermissions()
    else await retry(() => installSkill(src)) // restore overwritten skill
  }
}

Prevention

When it happens

Trigger: os-level copy of srcDir to SkillsDir()/<name> fails — destination disk full, permission denied on the skills directory, source file removed mid-copy, file lock contention, or a locked file on Windows.

Common situations: Read-only or permission-restricted workspace data directory; insufficient disk space; antivirus or another process holding files in the destination open on Windows; skill name colliding with a directory that cannot be fully removed.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/74bd02728cd81631. Report an issue: GitHub.

Appendix: source

Thrown at kernel/util/skill.go:836

				continue
			}
			name = filepath.Base(rel)
		}
		if verr := validateSkillName(name); verr != nil {
			logging.LogWarnf("skip invalid skill name [%s]: %s", name, verr)
			continue
		}

		destDir := filepath.Join(SkillsDir(), name)
		if err := os.MkdirAll(SkillsDir(), 0755); err != nil {
			return nil, err
		}
		// 覆盖式安装:先清旧目录
		if gulu.File.IsExist(destDir) {
			os.RemoveAll(destDir)
		}
		if err := filelock.Copy(srcDir, destDir); err != nil {
			return nil, fmt.Errorf("install skill %s failed: %s", name, err)
		}
		result.Names = append(result.Names, name)
		desc := fm["description"]
		if desc == "" {
			desc = firstLine(body)
		}
		result.Descriptions = append(result.Descriptions, desc)
	}
	if len(result.Names) == 0 {
		return nil, errors.New("no valid skill installed")
	}
	return result, nil
}

// installFromSingleSkillMD 把单个 SKILL.md 文本内容落地为一个 skill
func installFromSingleSkillMD(data []byte) (*InstallSkillResult, error) {
	content := string(data)
	fm, body := parseSkillFrontmatter(content)

View on GitHub (pinned to 9f775e8a12)