siyuan-note/siyuan · error
cannot write a skill directory
Error message
cannot write a skill directory
What it means
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.
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
Example fix
// before: Path = "skills/foo" where foo is a directory
root.ManageSkillFiles("write", "skills/foo", content, rev)
// after
root.ManageSkillFiles("write", "skills/foo/SKILL.md", content, rev) Defensive patterns
Strategy: validation
Validate before calling
info, err := root.Lstat(p); if err == nil && info.IsDir() { /* choose a file path or move/remove first */ } Try / catch
if err := ManageSkillFiles(req); err != nil { if strings.Contains(err.Error(), "cannot write a skill directory") { /* adjust path */ } } Prevention
- 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
When it happens
Trigger: Calling ManageSkillFiles with request.Action == "write" where request.Path resolves (via Lstat) to an existing directory inside the skill root.
Common situations: 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.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- Conf.Language(388) with escaped relative path…
- enable encrypted notebook failed: failed to persist key…
- failed to write CA certificate
- failed to write file
- remove legacy AI editor actions failed
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/dfcae968624729d3.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/skill_manage.go:404
}
}
}
if (request.Action == "move" || request.Action == "remove") && isManagedSkillManifest(request.Path) {
return ret, errors.New("rename or delete the skill directory instead of SKILL.md")
}
if info != nil {
revision, revisionErr := managedSkillRevision(root, request.Path)
if revisionErr != nil {
return ret, revisionErr
}
if request.Revision == "" || request.Revision != revision {
return ret, errors.New("skill changed; reload it before saving, renaming or deleting")
}
}
switch request.Action {
case "write":
if info != nil && info.IsDir() {
return ret, errors.New("cannot write a skill directory")
}
if err = writeManagedSkillSource(root, request.Path, request.Content, info == nil, request.Revision); err != nil {
return ret, err
}
ret.Revision = fmt.Sprintf("%x", sha256.Sum256([]byte(request.Content)))
return ret, nil
case "move":
if err = checkManagedSkillPath(root, request.Target); err != nil {
return ret, err
}
if path.Dir(request.Path) != path.Dir(request.Target) {
return ret, errors.New("skills and text files can only be renamed in the same directory")
}
if _, err = root.Lstat(request.Target); !errors.Is(err, os.ErrNotExist) {
return ret, errors.New("skill destination already exists or is inaccessible")
}
return ret, root.Rename(request.Path, request.Target)
case "remove":View on GitHub (pinned to 9f775e8a12)