benbjohnson/litestream · error
webdav: cannot read directory %q: %w
Error message
webdav: cannot read directory %q: %w
What it means
LTXFiles lists the LTX files for a compaction level by reading the directory <path>/<level> on the WebDAV server. Missing directories are treated as an empty file list (no error), but any other ReadDir failure (permissions, server error, timeout) is wrapped with this message.
Source
Thrown at webdav/replica_client.go:148
internal.OperationTotalCounterVec.WithLabelValues(ReplicaClientType, "DELETE").Inc()
return nil
}
func (c *ReplicaClient) LTXFiles(ctx context.Context, level int, seek ltx.TXID, _ bool) (_ ltx.FileIterator, err error) {
client, err := c.init(ctx)
if err != nil {
return nil, err
}
dir := litestream.LTXLevelDir(c.Path, level)
files, err := client.ReadDir(dir)
if err != nil {
if os.IsNotExist(err) || gowebdav.IsErrNotFound(err) {
return ltx.NewFileInfoSliceIterator(nil), nil
}
return nil, fmt.Errorf("webdav: cannot read directory %q: %w", dir, err)
}
infos := make([]*ltx.FileInfo, 0, len(files))
for _, fi := range files {
if fi.IsDir() {
continue
}
minTXID, maxTXID, err := ltx.ParseFilename(path.Base(fi.Name()))
if err != nil {
continue
} else if minTXID < seek {
continue
}
infos = append(infos, <x.FileInfo{
Level: level,
MinTXID: minTXID,View on GitHub (pinned to 4ed7a308f6)
Solutions
- Verify the directory is listable: curl -u user:pass -X PROPFIND https://host/<path>/0 -H 'Depth: 1'
- Check permissions on the LTX level directories for the configured WebDAV user
- Look at the wrapped error for the root cause (status code, timeout, TLS) and fix accordingly
- Retry on transient errors; note a 404 is already handled gracefully as an empty iterator
- If the replica tree is broken, re-seed it with a fresh 'litestream replicate' run
Defensive patterns
Strategy: fallback
Validate before calling
dir := litestream.LTXLevelDir(path, level)
req, _ := http.NewRequest(http.MethodPropfind, baseURL+dir, nil)
req.SetBasicAuth(user, pass)
resp, err := httpClient.Do(req)
if err != nil { return err }
resp.Body.Close()
if resp.StatusCode != 207 && resp.StatusCode != 404 {
return fmt.Errorf("PROPFIND %s: %s", dir, resp.Status)
} Try / catch
it, err := rc.LTXFiles(ctx, level)
if err != nil && strings.Contains(err.Error(), "cannot read directory") {
// fall back to empty listing and retry later, or surface the wrapped cause
return fmt.Errorf("listing level %d failed: %w", level, err)
} Prevention
- Ensure the WebDAV user can PROPFIND (read) the replica directories
- Expect level directories to be absent pre-first-replication — 404 is already handled as empty
- Retry on transient server/network errors; they surface wrapped here
- Keep server-side permissions stable across the whole LTX level tree
When it happens
Trigger: Listing replicas ('litestream replicas' / restore path / retention scans) when the WebDAV server returns a non-404 error for PROPFIND on the level directory: 403, 500, connection drop, or timeout.
Common situations: WebDAV user lacking read permission on the directory; server outage mid-listing; transient network failure; server-side path permissions changed after the replica was created; directory exists but PROPFIND depth is restricted by the server.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- webdav: cannot connect to server: %w
- webdav: cannot delete path %q: %w
- webdav: cannot write file %q: %w
- heartbeat URL must be a valid HTTP or HTTPS URL
- abs: cannot list blobs: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/2563f6168b9897d9.
Report an issue: GitHub.