{"record":{"id":"74bd02728cd81631","repo":"siyuan-note/siyuan","slug":"install-skill-s-failed-s","errorCode":null,"errorMessage":"install skill %s failed: %s","messagePattern":"install skill (.+?) failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill.go","lineNumber":836,"sourceCode":"\t\t\t\tcontinue\n\t\t\t}\n\t\t\tname = filepath.Base(rel)\n\t\t}\n\t\tif verr := validateSkillName(name); verr != nil {\n\t\t\tlogging.LogWarnf(\"skip invalid skill name [%s]: %s\", name, verr)\n\t\t\tcontinue\n\t\t}\n\n\t\tdestDir := filepath.Join(SkillsDir(), name)\n\t\tif err := os.MkdirAll(SkillsDir(), 0755); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// 覆盖式安装：先清旧目录\n\t\tif gulu.File.IsExist(destDir) {\n\t\t\tos.RemoveAll(destDir)\n\t\t}\n\t\tif err := filelock.Copy(srcDir, destDir); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"install skill %s failed: %s\", name, err)\n\t\t}\n\t\tresult.Names = append(result.Names, name)\n\t\tdesc := fm[\"description\"]\n\t\tif desc == \"\" {\n\t\t\tdesc = firstLine(body)\n\t\t}\n\t\tresult.Descriptions = append(result.Descriptions, desc)\n\t}\n\tif len(result.Names) == 0 {\n\t\treturn nil, errors.New(\"no valid skill installed\")\n\t}\n\treturn result, nil\n}\n\n// installFromSingleSkillMD 把单个 SKILL.md 文本内容落地为一个 skill\nfunc installFromSingleSkillMD(data []byte) (*InstallSkillResult, error) {\n\tcontent := string(data)\n\tfm, body := parseSkillFrontmatter(content)","sourceCodeStart":818,"sourceCodeEnd":854,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/skill.go#L818-L854","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Pre-flight: skills dir must be writable and have headroom\nconst dir = getSkillsDir()\nawait fs.promises.access(dir, fs.constants.W_OK).catch(() => { throw new Error(\"skills dir not writable\") })\nconst stat = await fs.promises.statfs(dir)\nif (stat.bavail * stat.bsize < 50 * 1024 * 1024) throw new Error(\"low disk space\")","typeGuard":null,"tryCatchPattern":"try {\n  await installSkill(src)\n} catch (e) {\n  if (String(e).startsWith(\"install skill\") && String(e).includes(\"failed:\")) {\n    const cause = String(e).split(\"failed: \")[1]\n    if (/no space left/i.test(cause)) freeDiskSpaceAndRetry()\n    else if (/permission denied/i.test(cause)) fixWorkspacePermissions()\n    else await retry(() => installSkill(src)) // restore overwritten skill\n  }\n}","preventionTips":["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"],"tags":["filesystem","install","copy","permissions"],"backgroundTag":"file-write-failed","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}