benbjohnson/litestream · error

webdav: cannot read file %q: %w

Error message

webdav: cannot read file %q: %w

What it means

OpenLTXFile with a known size uses client.ReadStreamRange to fetch only the requested byte range. If the range request fails and the object is not simply missing, the error is wrapped as 'cannot read file'. Note: os.ErrNotExist is returned unchanged when the server reports the file not found, so this error means a real read failure.

Source

Thrown at webdav/replica_client.go:317

}

func (c *ReplicaClient) OpenLTXFile(ctx context.Context, level int, minTXID, maxTXID ltx.TXID, offset, size int64) (_ io.ReadCloser, err error) {
	client, err := c.init(ctx)
	if err != nil {
		return nil, err
	}

	filename := litestream.LTXFilePath(c.Path, level, minTXID, maxTXID)

	internal.OperationTotalCounterVec.WithLabelValues(ReplicaClientType, "GET").Inc()

	if size > 0 {
		rc, err := client.ReadStreamRange(filename, offset, size)
		if err != nil {
			if os.IsNotExist(err) || gowebdav.IsErrNotFound(err) {
				return nil, os.ErrNotExist
			}
			return nil, fmt.Errorf("webdav: cannot read file %q: %w", filename, err)
		}
		return internal.LimitReadCloser(rc, size), nil
	}

	if offset > 0 {
		rc, err := client.ReadStream(filename)
		if err != nil {
			if os.IsNotExist(err) || gowebdav.IsErrNotFound(err) {
				return nil, os.ErrNotExist
			}
			return nil, fmt.Errorf("webdav: cannot read file %q: %w", filename, err)
		}

		if _, err := io.CopyN(io.Discard, rc, offset); err != nil {
			if err == io.EOF || err == io.ErrUnexpectedEOF {
				_ = rc.Close()
				return io.NopCloser(bytes.NewReader(nil)), nil
			}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Confirm the server supports Range requests (check for Accept-Ranges in a HEAD response)
  2. Check credentials and URL correctness in the litestream config
  3. Retry the restore; transient network failures are often intermittent
  4. Bypass proxies or configure them to pass Range headers through
Defensive patterns

Strategy: retry

Validate before calling

// verify server supports ranges before relying on partial reads
resp, err := http.Head(fileURL)
if err == nil && resp.Header.Get("Accept-Ranges") != "bytes" { log.Warn("WebDAV server lacks Range support") }

Try / catch

rc, err := OpenLTXFile(ctx, info)
if err != nil {
    if errors.Is(err, os.ErrNotExist) { /* object genuinely missing */ }
    else if isTransient(err) { retry with backoff }
}

Prevention

When it happens

Trigger: ReadStreamRange (HTTP Range GET) returns a non-404 error: server not honoring Range headers, TLS failures, auth rejection, or connection errors during the ranged read.

Common situations: WebDAV server without Range support (litestream relies on it for partial reads), intermediate proxies stripping Range headers, expired sessions/token mid-restore.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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