benbjohnson/litestream · error

abs: cannot delete ltx file %q: %w

Error message

abs: cannot delete ltx file %q: %w

What it means

This error is returned by ReplicaClient.DeleteLTXFiles when an LTX blob deletion fails in Azure Blob Storage. Before wrapping, BlobAlreadyExists-style 'not found' responses are filtered out via isNotExists(err), so reaching this error means the DELETE API call failed for a non-404 reason (auth, network, lease, permissions). The original Azure SDK error is wrapped with the blob key for diagnosis.

Source

Thrown at abs/replica_client.go:307

	return resp.Body, nil
}

// DeleteLTXFiles deletes LTX files.
func (c *ReplicaClient) DeleteLTXFiles(ctx context.Context, a []*ltx.FileInfo) error {
	if err := c.Init(ctx); err != nil {
		return err
	}

	for _, info := range a {
		key := litestream.LTXFilePath(c.Path, info.Level, info.MinTXID, info.MaxTXID)

		c.logger.Debug("deleting ltx file", "level", info.Level, "minTXID", info.MinTXID, "maxTXID", info.MaxTXID, "key", key)

		_, err := c.client.DeleteBlob(ctx, c.Bucket, key, nil)
		if isNotExists(err) {
			continue
		} else if err != nil {
			return fmt.Errorf("abs: cannot delete ltx file %q: %w", key, err)
		}

		internal.OperationTotalCounterVec.WithLabelValues(ReplicaClientType, "DELETE").Inc()
	}

	return nil
}

// DeleteAll deletes all LTX files.
func (c *ReplicaClient) DeleteAll(ctx context.Context) error {
	if err := c.Init(ctx); err != nil {
		return err
	}

	// List all blobs with the configured path prefix
	prefix := "/"
	if c.Path != "" {
		prefix = strings.TrimSuffix(c.Path, "/") + "/"

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Check that the storage account/container credentials allow delete (Storage Blob Data Contributor role, or SAS with 'd' permission) — inspect the wrapped azblob error message for the actual HTTP status
  2. Verify no immutability policy, legal hold, or active lease on the blob in the Azure portal
  3. Test connectivity to the storage account (endpoint, DNS, firewall rules) and retry — transient failures are safe to re-run since DeleteLTXFiles skips already-missing blobs
  4. If the blob is soft-deleted and unresolvable, use 'litestream reset <db>' to clear local state, or delete the blob explicitly in the portal

Example fix

// before
_, err := c.client.DeleteBlob(ctx, c.Bucket, key, nil) // account lacks delete permission
// after
// fix credentials/SAS, then retry:
err := client.DeleteLTXFiles(ctx, fileInfos)
if err != nil {
	if errors.Is(err, azureblob.ErrNoDeletePermission) { /* grant Storage Blob Data Contributor */ }
	return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Go
func canDelete(ctx context.Context, c *abs.ReplicaClient) error {
	_, err := c.client.DeleteBlob(ctx, c.Bucket, "__perm_probe__", nil) // expect NotExists, not 403
	if err != nil && !isNotExists(err) { return err }
	return nil
}

Type guard

// Go
func isPermissionError(err error) bool {
	var re *azcore.ResponseError
	return errors.As(err, &re) && re.StatusCode == http.StatusForbidden
}

Try / catch

if err := client.DeleteLTXFiles(ctx, infos); err != nil {
	if isPermissionError(err) { /* fix credentials/role */ }
	else if transient(err) { /* retry with backoff — idempotent */ }
	return err
}

Prevention

When it happens

Trigger: Calling DeleteLTXFiles(ctx, fileInfos) while an azblob DeleteBlob call fails with a non-NotExists error — e.g. the container/account credentials lack delete permission, a lease is held on the blob, a SAS token lacks delete scope, the storage account is unavailable, or the context is cancelled mid-delete.

Common situations: Misconfigured connection string or SAS token without delete (d) permission; container-level immutable/lock policies on the storage account; network or DNS outage during retention cleanup; using a read-only account; blob soft-delete plus immutability policies blocking hard deletes.

Related errors


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