siyuan-note/siyuan · error
failed to write file: %w
Error message
failed to write file: %w
What it means
Thrown when filelock.WriteFile fails to write the bytes to the target file. filelock is SiYuan's cross-platform locked file writer; it returns an error on permission denial, disk full, read-only filesystem, the target being a directory, or a lock acquisition failure. The error is wrapped with %w so the underlying cause is preserved.
Source
Thrown at kernel/plugin/api_storage.go:282
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 {
logging.LogErrorf("[plugin:%s] siyuan.storage.put worker run: %v", p.Name, runErr)
if rejectErr := reject(rt.NewGoError(runErr)); rejectErr != nil {
logging.LogErrorf("[plugin:%s] siyuan.storage.put reject: %v", p.Name, rejectErr)
}View on GitHub (pinned to 251596fc0d)
Solutions
- Unwrap the error to read the OS-level cause (ENOENT, EACCES, ENOSPC, EISDIR).
- Confirm the target path is not a directory; if so, choose a different path or remove the directory.
- Free disk space or raise the storage quota.
- Ensure only one kernel process owns the workspace to avoid lock contention.
Example fix
// before await siyuan.storage.put(path, body); // path is a directory // after // pick a file path that is not an existing directory: await siyuan.storage.put(path + '.json', body);
Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight: ensure target is not an existing directory before writing.
Try / catch
try {
await siyuan.storage.put(path, body);
} catch (e) {
if (/failed to write file/.test(String(e))) {
const cause = String(e);
if (/EISDIR|is a directory/.test(cause)) { /* pick a different path */ }
else if (/ENOSPC/.test(cause)) { /* free disk space */ }
else throw e;
} else throw e;
} Prevention
- Do not reuse a directory path as a file path.
- Ensure only one kernel process owns the workspace to avoid filelock contention.
- Monitor free disk space on the storage volume.
When it happens
Trigger: put('state.json', body) where 'state.json' is actually a directory; disk full; permission denied; the file is held with an incompatible lock by another process; storage volume mounted read-only.
Common situations: Plugin overwrote a path that a prior step created as a directory; shared workspace accessed by two kernel instances racing on the same file; disk quota exhausted on the storage volume.
Related errors
- failed to make directory: %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/0d8deb3fc9dbfb2f.
Report an issue: GitHub.