benbjohnson/litestream · error
webdav: cannot read directory
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.
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
- webdav: cannot delete path
- webdav: cannot write file
- abs: cannot list blobs
- cannot specify url & path for webdav replica
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/2563f6168b9897d9.
Report an issue: GitHub.
Appendix: 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)