benbjohnson/litestream · error

webdav: cannot delete path %q: %w

Error message

webdav: cannot delete path %q: %w

What it means

DeleteAll removes the replica's entire path tree on the WebDAV server via client.RemoveAll(c.Path). If the removal fails for any reason other than 'path does not exist' (os.IsNotExist or gowebdav.IsErrNotFound), the error is wrapped with this message. It means the server refused or failed the recursive delete — typically permissions, a lock, or a transient server error.

Source

Thrown at webdav/replica_client.go:128

	c.client.SetTimeout(c.Timeout)

	if err := c.client.Connect(); err != nil {
		c.client = nil
		return nil, fmt.Errorf("webdav: cannot connect to server: %w", err)
	}

	return c.client, nil
}

func (c *ReplicaClient) DeleteAll(ctx context.Context) error {
	client, err := c.init(ctx)
	if err != nil {
		return err
	}

	if err := client.RemoveAll(c.Path); err != nil && !os.IsNotExist(err) && !gowebdav.IsErrNotFound(err) {
		return fmt.Errorf("webdav: cannot delete path %q: %w", c.Path, err)
	}

	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

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Check that the configured WebDAV user has DELETE/PUT permission on the path (try deleting a test file with curl -X DELETE)
  2. Inspect the wrapped error for the HTTP status: 403 = permissions, 507 = quota, 423 = locked
  3. Verify nothing else (another Litestream instance or sync client) is holding locks on the tree
  4. Retry the operation if the server error was transient; partial deletions are safe to re-run since not-found is tolerated
  5. As a last resort delete the collection server-side (admin panel/SSH) and re-run
Defensive patterns

Strategy: try-catch

Validate before calling

// probe delete permission with a throwaway object first
probe := c.Path + "/.litestream-permcheck"
if err := wc.Write(probe, strings.NewReader(""), nil); err != nil {
    return fmt.Errorf("no write/delete permission on %s: %w", c.Path, err)
}
wc.Remove(probe)

Try / catch

err := rc.DeleteAll(ctx)
if err != nil && strings.Contains(err.Error(), "cannot delete path") {
    var sd *gowebdav.StatusError
    if errors.As(err, &sd) && sd.Status == http.StatusForbidden {
        // escalate permissions or clean up server-side
    }
}

Prevention

When it happens

Trigger: Calling DeleteAll (directly or via 'litestream replicate' teardown / replica reset paths) when the WebDAV account lacks write/delete permission on the path, the server returns 403/500/507, or a file is locked by another client.

Common situations: WebDAV user without delete rights on the collection; read-only mount on the server; another backup client holding a lock; quota/server-side errors during large recursive deletes; partial deletes leaving the collection in a locked state.

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


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