benbjohnson/litestream · error

create hydration directory: %w

Error message

create hydration directory: %w

What it means

Hydrator.Init creates the parent directory of the hydration file with os.MkdirAll before opening or creating the file. This error wraps that failure. Hydration stores a local materialized copy of the remote database, so its directory must exist and be writable before initialization can proceed.

Source

Thrown at vfs.go:681

	client     ReplicaClient
	logger     *slog.Logger
}

// NewHydrator creates a new Hydrator instance.
func NewHydrator(path string, persistent bool, pageSize uint32, client ReplicaClient, logger *slog.Logger) *Hydrator {
	return &Hydrator{
		path:       path,
		persistent: persistent,
		pageSize:   pageSize,
		client:     client,
		logger:     logger,
	}
}

// Init opens or creates the hydration file.
func (h *Hydrator) Init() error {
	if err := os.MkdirAll(filepath.Dir(h.path), 0755); err != nil {
		return fmt.Errorf("create hydration directory: %w", err)
	}

	if h.persistent {
		if txid, err := h.loadMeta(); err == nil {
			if _, statErr := os.Stat(h.path); statErr == nil {
				file, err := os.OpenFile(h.path, os.O_RDWR, 0600)
				if err != nil {
					return fmt.Errorf("open persistent hydration file: %w", err)
				}
				h.file = file
				h.txid = txid
				return nil
			}
		}
		if err := os.Remove(h.metaPath()); err != nil && !os.IsNotExist(err) {
			return fmt.Errorf("remove stale hydration meta: %w", err)
		}
	}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Create the parent directory manually and chown it to the process user (mkdir -p <dir> && chown app:app <dir>).
  2. Point hydrationPath at a location the process can write (e.g. a mounted writable volume in containers).
  3. Check that no path component is an existing regular file; rename or remove it.
  4. If the mount is read-only, remount rw or select another directory.

Example fix

// before
hydrator := NewHydrator("/var/lib/litestream/hydration/app.db") // no permission

// after
os.MkdirAll("/var/lib/litestream/hydration", 0o755) // as setup step, with correct ownership
hydrator := NewHydrator("/var/lib/litestream/hydration/app.db")
Defensive patterns

Strategy: validation

Validate before calling

dir := filepath.Dir(hydrationPath)
if err := os.MkdirAll(dir, 0o755); err != nil {
    return fmt.Errorf("hydration dir %q not preparable: %w", dir, err)
}
probe, err := os.CreateTemp(dir, "probe-*")
if err != nil { return err }
probe.Close(); os.Remove(probe.Name())

Try / catch

if err := hydrator.Init(); err != nil && strings.Contains(err.Error(), "create hydration directory") {
    // fix ownership/permissions on dir, then retry Init
}

Prevention

When it happens

Trigger: Calling Hydrator.Init (via opening the DB with hydration enabled) when the directory containing the configured hydration path cannot be created — missing permission on the parent, path component is a file, or read-only filesystem.

Common situations: hydrationPath pointing under a read-only mount; typo making the parent path contain an existing file (e.g. /data/db where db is a file); running under a non-root container user with no write access to /var/lib/litestream.

Understand the failure class

Background: mkdir permission denied (EACCES): failed to create directory errors explained — this error's family across 32 libraries.

Related errors


AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/0a83809d89f5e3f1. Report an issue: GitHub.