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

  1. Unwrap the error to read the OS-level cause (ENOENT, EACCES, ENOSPC, EISDIR).
  2. Confirm the target path is not a directory; if so, choose a different path or remove the directory.
  3. Free disk space or raise the storage quota.
  4. 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

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


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/0d8deb3fc9dbfb2f. Report an issue: GitHub.