siyuan-note/siyuan · error
create a skill before creating its subdirectories
Error message
create a skill before creating its subdirectories
What it means
The "mkdir" action only creates subdirectories inside an existing skill; a path without "/" would be a top-level skill name. This error tells the caller that skills themselves must be created with the "create" action before their subdirectories can be made.
Solutions
- Create the skill first with Action "create", then call mkdir with a path that includes the skill prefix
- Verify request.Path contains at least one "/" before calling mkdir
Example fix
// before
ManageSkillFiles(SkillFileRequest{Action: "mkdir", Path: "my-skill"})
// after
ManageSkillFiles(SkillFileRequest{Action: "create", Path: "my-skill"})
ManageSkillFiles(SkillFileRequest{Action: "mkdir", Path: "my-skill/scripts"}) Defensive patterns
Strategy: validation
Validate before calling
if !strings.Contains(req.Path, "/") {
// create the skill first with Action "create", then mkdir "<skill>/<sub>"
} Type guard
func isSkillSubpath(p string) bool { return strings.Contains(p, "/") } Try / catch
if err := ManageSkillFiles(req); err != nil && strings.Contains(err.Error(), "create a skill before creating") {
// create the skill, then retry mkdir
} Prevention
- Ensure the parent skill exists before any mkdir call
- Always build subdirectory paths as "<skill>/<sub>"
- Distinguish create (skill) from mkdir (subdirectory) in client code
When it happens
Trigger: ManageSkillFiles{Action:"mkdir"} with a slash-free request.Path such as "my-skill" instead of "my-skill/subdir"; trying to create a new skill directory via mkdir.
Common situations: Confusing "create" (new skill + SKILL.md) with "mkdir" (subdirectory); generic file-management code calling mkdir for every directory level including the top level.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- unsupported skill operation
- only text files inside a skill can be edited
- rename or delete the skill directory instead of SKILL.md
- Access to encrypted notebook data is not supported via this…
- AI editor action must not be empty
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/e5d8b4b26206c5bb.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/skill_manage.go:356
}
tmp := ".skill-" + ast.NewNodeID()
if err = root.Mkdir(tmp, 0755); err != nil {
return ret, err
}
defer root.RemoveAll(tmp)
if err = writeManagedSkillSource(root, path.Join(tmp, "SKILL.md"), content, true, ""); err != nil {
return ret, err
}
if err = root.Rename(tmp, request.Path); err != nil {
return ret, err
}
ret.Content = &content
ret.Revision = fmt.Sprintf("%x", sha256.Sum256([]byte(content)))
return ret, nil
}
if request.Action == "mkdir" {
if !strings.Contains(request.Path, "/") {
return ret, errors.New("create a skill before creating its subdirectories")
}
return ret, root.Mkdir(request.Path, 0755)
}
if statErr != nil && !(request.Action == "write" && request.Revision == "" && errors.Is(statErr, os.ErrNotExist)) {
return ret, statErr
}
if request.Action == "read" {
if !info.IsDir() && strings.Contains(request.Path, "/") {
content, readErr := readManagedSkillSource(root, request.Path)
if readErr == nil {
ret.Content = &content
ret.Revision = fmt.Sprintf("%x", sha256.Sum256([]byte(content)))
return ret, nil
}
ret.ReadOnlyReason = skillSourceReadOnlyReason(readErr)
if ret.ReadOnlyReason == "" {
return ret, readErr
}View on GitHub (pinned to 9f775e8a12)