{"record":{"id":"dfcae968624729d3","repo":"siyuan-note/siyuan","slug":"cannot-write-a-skill-directory","errorCode":null,"errorMessage":"cannot write a skill directory","messagePattern":"cannot write a skill directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill_manage.go","lineNumber":404,"sourceCode":"\t\t\t}\n\t\t}\n\t}\n\tif (request.Action == \"move\" || request.Action == \"remove\") && isManagedSkillManifest(request.Path) {\n\t\treturn ret, errors.New(\"rename or delete the skill directory instead of SKILL.md\")\n\t}\n\tif info != nil {\n\t\trevision, revisionErr := managedSkillRevision(root, request.Path)\n\t\tif revisionErr != nil {\n\t\t\treturn ret, revisionErr\n\t\t}\n\t\tif request.Revision == \"\" || request.Revision != revision {\n\t\t\treturn ret, errors.New(\"skill changed; reload it before saving, renaming or deleting\")\n\t\t}\n\t}\n\tswitch request.Action {\n\tcase \"write\":\n\t\tif info != nil && info.IsDir() {\n\t\t\treturn ret, errors.New(\"cannot write a skill directory\")\n\t\t}\n\t\tif err = writeManagedSkillSource(root, request.Path, request.Content, info == nil, request.Revision); err != nil {\n\t\t\treturn ret, err\n\t\t}\n\t\tret.Revision = fmt.Sprintf(\"%x\", sha256.Sum256([]byte(request.Content)))\n\t\treturn ret, nil\n\tcase \"move\":\n\t\tif err = checkManagedSkillPath(root, request.Target); err != nil {\n\t\t\treturn ret, err\n\t\t}\n\t\tif path.Dir(request.Path) != path.Dir(request.Target) {\n\t\t\treturn ret, errors.New(\"skills and text files can only be renamed in the same directory\")\n\t\t}\n\t\tif _, err = root.Lstat(request.Target); !errors.Is(err, os.ErrNotExist) {\n\t\t\treturn ret, errors.New(\"skill destination already exists or is inaccessible\")\n\t\t}\n\t\treturn ret, root.Rename(request.Path, request.Target)\n\tcase \"remove\":","sourceCodeStart":386,"sourceCodeEnd":422,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/skill_manage.go#L386-L422","documentation":"ManageSkillFiles' 'write' action refuses to overwrite a path that currently exists as a directory. The skill workspace treats directories and files as distinct managed resources, so a write to a directory path is rejected instead of silently truncating or replacing the directory. This protects skill content from accidental destruction.","triggerScenarios":"Calling ManageSkillFiles with request.Action == \"write\" where request.Path resolves (via Lstat) to an existing directory inside the skill root.","commonSituations":"A path previously created as a directory (e.g. a skill folder) is targeted for write; stale directory left from a failed earlier operation; a path computed with a missing file component like 'skills/foo' when 'foo' is a directory.","solutions":["Use the 'move' action or remove the directory before writing to that path","Write to a file path inside the directory instead (e.g. skills/foo/SKILL.md)","Check existence/type of the target path first and pick a distinct file name"],"exampleFix":"// before: Path = \"skills/foo\" where foo is a directory\nroot.ManageSkillFiles(\"write\", \"skills/foo\", content, rev)\n// after\nroot.ManageSkillFiles(\"write\", \"skills/foo/SKILL.md\", content, rev)","handlingStrategy":"validation","validationCode":"info, err := root.Lstat(p); if err == nil && info.IsDir() { /* choose a file path or move/remove first */ }","typeGuard":null,"tryCatchPattern":"if err := ManageSkillFiles(req); err != nil { if strings.Contains(err.Error(), \"cannot write a skill directory\") { /* adjust path */ } }","preventionTips":["Lstat the target path before writing and branch on IsDir","Never point write at directory-like paths","Use explicit file names (e.g. SKILL.md) rather than directory stems"],"tags":["skill-management","file-write","path-conflict"],"backgroundTag":"incompatible-source-type","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"}