siyuan-note/siyuan · error
failed to make directory: %w
Error message
failed to make directory: %w
What it means
Thrown when os.MkdirAll on the parent directory of the target file fails. Before writing the file, put ensures filepath.Dir(abs) exists with mode 0755; if MkdirAll returns an error it is wrapped (note %w, so the underlying error is unwrap-able). Typical causes: permission denied on an ancestor, a parent path component that is a file rather than a directory, read-only filesystem, or invalid path syntax on the OS.
Source
Thrown at kernel/plugin/api_storage.go:278
err = fmt.Errorf("panic during siyuan.storage.put: %v", r)
}
p.worker.Run(func(rt *goja.Runtime) (_ any, _ error) {
if lo.IsNil(err) {
if resolveErr := resolve(result); resolveErr != nil {
logging.LogErrorf("[plugin:%s] siyuan.storage.put resolve: %v", p.Name, resolveErr)
}
} else {
if rejectErr := reject(rt.NewGoError(err)); rejectErr != nil {
logging.LogErrorf("[plugin:%s] siyuan.storage.put reject: %v", p.Name, rejectErr)
}
}
return
}, nil)
}()
if mkdirErr := os.MkdirAll(filepath.Dir(abs), 0755); mkdirErr != nil {
err = fmt.Errorf("failed to make directory: %w", mkdirErr)
return
}
if writeErr := filelock.WriteFile(abs, []byte(content)); writeErr != nil {
err = fmt.Errorf("failed to write file: %w", writeErr)
return
}
return
}()
return
}, func(rt *goja.Runtime, result any, err error) {
if !lo.IsNil(err) {
if rejectErr := reject(rt.NewGoError(err)); rejectErr != nil {
logging.LogErrorf("[plugin:%s] siyuan.storage.put reject: %v", p.Name, rejectErr)
}
}
})
if runErr != nil {View on GitHub (pinned to 251596fc0d)
Solutions
- Inspect the wrapped error (errors.Unwrap or err.cause) for the OS-level reason.
- Verify the storage directory is writable by the kernel process and no ancestor is a file.
- Remount the storage volume read-write if it was accidentally mounted read-only.
- Avoid creating overly deep or OS-illegal path segments.
Example fix
// before
await siyuan.storage.put('data/state.json', body); // 'data' is a file
// after
// rename or remove the conflicting 'data' file, then:
await siyuan.storage.put('data/state.json', body); Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight: avoid nesting under a path that is a file. // Keep storage paths shallow and do not reuse a filename as a directory.
Try / catch
try {
await siyuan.storage.put(path, body);
} catch (e) {
if (/failed to make directory/.test(String(e))) {
console.error('mkdir failed for', path, e);
// simplify the path or fix the conflicting ancestor
}
throw e;
} Prevention
- Never create a file and a directory at the same relative path.
- Ensure the storage volume is writable and not mounted read-only.
- Keep directory depth modest to avoid ancestor permission issues.
When it happens
Trigger: put('a/b/c/state.json', body) where 'a/b' exists as a plain file; storage dir on a read-only mount; permission bits on the storage root prevent the plugin's user from creating directories; path contains characters illegal on Windows.
Common situations: Plugin nests deeply and one ancestor was created as a file by an earlier bug; containerized deployment mounted the storage volume read-only; UID mismatch between kernel process and storage dir owner.
Related errors
- failed to write file: %w
- failed to add storage path to watcher: %v
- The current kernel is in read-only mode, storage.put is not
- The current kernel is in read-only mode, storage.remove is n
- cannot remove storage root
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/5045faeefb6849a0.
Report an issue: GitHub.