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
- Read the wrapped cause: 'no space left on device' means free disk space; 'permission denied' means fix ownership/permissions of the workspace skills directory
- Close other SiYuan instances/processes that may lock the skills directory, then retry
- Ensure the workspace is writable by the user running the kernel
- 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
- Keep the workspace data directory writable by the kernel's user account
- Monitor free disk space before installing skills
- Avoid running multiple SiYuan instances against the same workspace during installs (file locks)
- Remember installs are overwrite-style: re-run the install to recover a partially removed skill
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
- Conf.Language(14) (copy resource failed: )
- copy asset [ ] to [ ] failed
- copy data failed
- create AI editor actions directory failed
- create conf dir failed
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)