benbjohnson/litestream · error

webdav: cannot create parent directory %q: %w

Error message

webdav: cannot create parent directory %q: %w

What it means

WriteLTXFile ensures the destination directory exists on the WebDAV server via client.MkdirAll before uploading. This error wraps that failure: the server rejected directory creation (permissions, locked collection, or HTTP-level problem).

Source

Thrown at webdav/replica_client.go:278

	}
	defer func() {
		_ = tmpFile.Close()
		_ = os.Remove(tmpFile.Name())
	}()

	fullReader := io.MultiReader(&buf, rd)

	size, err := io.Copy(tmpFile, fullReader)
	if err != nil {
		return nil, fmt.Errorf("webdav: cannot copy to temp file: %w", err)
	}

	if _, err := tmpFile.Seek(0, io.SeekStart); err != nil {
		return nil, fmt.Errorf("webdav: cannot seek temp file: %w", err)
	}

	if err := client.MkdirAll(path.Dir(filename), 0755); err != nil {
		return nil, fmt.Errorf("webdav: cannot create parent directory %q: %w", path.Dir(filename), err)
	}

	// Upload with Content-Length header using seekable temp file.
	// WriteStreamWithLength requires both a seekable reader and known size,
	// which we now have from the temp file. This avoids chunked encoding
	// and ensures reliable uploads across all WebDAV server configurations.
	if err := client.WriteStreamWithLength(filename, tmpFile, size, 0644); err != nil {
		return nil, fmt.Errorf("webdav: cannot write file %q: %w", filename, err)
	}

	internal.OperationTotalCounterVec.WithLabelValues(ReplicaClientType, "PUT").Inc()
	internal.OperationBytesCounterVec.WithLabelValues(ReplicaClientType, "PUT").Add(float64(size))

	return &ltx.FileInfo{
		Level:     level,
		MinTXID:   minTXID,
		MaxTXID:   maxTXID,
		Size:      size,

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Verify the WebDAV username/password has write permission on the target path
  2. Check that no file exists at a path segment that must be a directory
  3. Test manually: curl -X MKCOL against the parent path to see the server's response
  4. Correct the configured replica path/URL in the litestream config

Example fix

// before litestream.yml
url: webdav://user:pass@dav.example.com/wrong-locked-path/db
// after
url: webdav://user:pass@dav.example.com/litestream/db
Defensive patterns

Strategy: try-catch

Validate before calling

client, _ := gowebdav.NewClient(serverURL, user, pass)
client.Connect() // fails early on auth problems
if err := client.MkdirAll(basePath, 0755); err != nil { return fmt.Errorf("precheck mkdir failed: %w", err) }

Try / catch

if err := writeLTX(ctx); err != nil {
    var pe *goserver.PathError
    if strings.Contains(err.Error(), "cannot create parent directory") { verify credentials and that no file blocks the path segment }
}

Prevention

When it happens

Trigger: Uploading to a path whose parent cannot be created on the WebDAV server: insufficient WebDAV credentials, read-only collection, or the server rejecting MKCOL requests for the path prefix.

Common situations: WebDAV user lacking write rights to the target collection, misconfigured path in the litestream config (e.g. wrong base URL segment), servers that disallow MKCOL at certain depths, or an intermediate path segment existing as a FILE not a directory.

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/20e3a68980db2d89. Report an issue: GitHub.