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

  1. Inspect the wrapped error (errors.Unwrap or err.cause) for the OS-level reason.
  2. Verify the storage directory is writable by the kernel process and no ancestor is a file.
  3. Remount the storage volume read-write if it was accidentally mounted read-only.
  4. 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

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


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