{"record":{"id":"28d0051bdcd8fca1","repo":"siyuan-note/siyuan","slug":"skill-directory-contains-a-link-or-special-file","errorCode":null,"errorMessage":"skill directory contains a link or special file","messagePattern":"skill directory contains a link or special file","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill_manage.go","lineNumber":138,"sourceCode":"}\n\nfunc isManagedSkillManifest(p string) bool {\n\treturn strings.Count(p, \"/\") == 1 && strings.EqualFold(path.Base(p), \"SKILL.md\")\n}\n\n// 目录版本包含每个资源的内容哈希，保持大小和修改时间不变的外部编辑也会产生冲突。\nfunc managedSkillRevision(root *os.Root, p string) (string, error) {\n\th := sha256.New()\n\terr := fs.WalkDir(root.FS(), p, func(name string, entry fs.DirEntry, walkErr error) error {\n\t\tif walkErr != nil {\n\t\t\treturn walkErr\n\t\t}\n\t\tinfo, err := entry.Info()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif info.Mode()&os.ModeSymlink != 0 || (!info.IsDir() && !info.Mode().IsRegular()) {\n\t\t\treturn errors.New(\"skill directory contains a link or special file\")\n\t\t}\n\t\tif info.IsDir() {\n\t\t\tfmt.Fprintf(h, \"directory\\x00%s\\x00\", name)\n\t\t\treturn nil\n\t\t}\n\t\tfile, err := root.Open(name)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\thash := sha256.New()\n\t\t_, copyErr := io.Copy(hash, file)\n\t\tcloseErr := file.Close()\n\t\tif copyErr != nil {\n\t\t\treturn copyErr\n\t\t}\n\t\tif closeErr != nil {\n\t\t\treturn closeErr\n\t\t}","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/skill_manage.go#L120-L156","documentation":"managedSkillRevision computes a content hash of a skill directory used as an optimistic-concurrency revision. While walking the tree it refuses to hash any entry that is a symlink or a non-regular, non-directory file, producing this error. This guarantees revisions only cover real skill content and blocks link/special-file escapes inside skill directories.","triggerScenarios":"Calling ManageSkillFiles read/write/move/remove on an existing path whose directory tree contains a symlink, FIFO, socket or device file; the hash walk (fs.WalkDir + entry.Info) hits that entry. Also surfaces indirectly via writeManagedSkillSource's re-validation before a replace rename.","commonSituations":"Users symlink asset or script files into a skill folder to avoid duplication; a sync tool materialized junctions/links; a node_modules-style tree with symlinks was copied into the skill directory.","solutions":["Find the offending entry in the skill directory tree and replace the symlink/special file with a regular copy of the file","Remove unrelated linked content from the skill directory","Retry the operation once the tree contains only regular files and directories"],"exampleFix":"// before\nln -s ../../assets/prompt.txt my-skill/assets/prompt.txt\n// after\ncp ../../assets/prompt.txt my-skill/assets/prompt.txt","handlingStrategy":"validation","validationCode":"func treeHasLinks(root *os.Root, dir string) error {\n  return fs.WalkDir(root.FS(), dir, func(p string, d fs.DirEntry, err error) error {\n    if err != nil { return err }\n    info, err := d.Info()\n    if err != nil { return err }\n    if info.Mode()&os.ModeSymlink != 0 || (!info.IsDir() && !info.Mode().IsRegular()) {\n      return fmt.Errorf(\"link/special file at %s\", p)\n    }\n    return nil\n  })\n}","typeGuard":"func isPlainEntry(d fs.DirEntry) bool {\n  return d.IsDir() || d.Type().IsRegular()\n}","tryCatchPattern":"if err := ManageSkillFiles(req); err != nil && strings.Contains(err.Error(), \"link or special file\") {\n  // walk the skill dir and replace links with copies\n}","preventionTips":["Copy, don't link, shared assets into skill directories","Run link-scanning before zipping/unzipping or syncing skill folders","Exclude node_modules-like trees from skill directories"],"tags":["filesystem","symlink","revision-conflict","skills"],"backgroundTag":"path-traversal-blocked","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"}