{"record":{"id":"33e17b8df943503c","repo":"benbjohnson/litestream","slug":"abs-cannot-delete-ltx-file-q-w","errorCode":null,"errorMessage":"abs: cannot delete ltx file %q: %w","messagePattern":"abs: cannot delete ltx file %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"abs/replica_client.go","lineNumber":307,"sourceCode":"\treturn resp.Body, nil\n}\n\n// DeleteLTXFiles deletes LTX files.\nfunc (c *ReplicaClient) DeleteLTXFiles(ctx context.Context, a []*ltx.FileInfo) error {\n\tif err := c.Init(ctx); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, info := range a {\n\t\tkey := litestream.LTXFilePath(c.Path, info.Level, info.MinTXID, info.MaxTXID)\n\n\t\tc.logger.Debug(\"deleting ltx file\", \"level\", info.Level, \"minTXID\", info.MinTXID, \"maxTXID\", info.MaxTXID, \"key\", key)\n\n\t\t_, err := c.client.DeleteBlob(ctx, c.Bucket, key, nil)\n\t\tif isNotExists(err) {\n\t\t\tcontinue\n\t\t} else if err != nil {\n\t\t\treturn fmt.Errorf(\"abs: cannot delete ltx file %q: %w\", key, err)\n\t\t}\n\n\t\tinternal.OperationTotalCounterVec.WithLabelValues(ReplicaClientType, \"DELETE\").Inc()\n\t}\n\n\treturn nil\n}\n\n// DeleteAll deletes all LTX files.\nfunc (c *ReplicaClient) DeleteAll(ctx context.Context) error {\n\tif err := c.Init(ctx); err != nil {\n\t\treturn err\n\t}\n\n\t// List all blobs with the configured path prefix\n\tprefix := \"/\"\n\tif c.Path != \"\" {\n\t\tprefix = strings.TrimSuffix(c.Path, \"/\") + \"/\"","sourceCodeStart":289,"sourceCodeEnd":325,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/abs/replica_client.go#L289-L325","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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","Verify no immutability policy, legal hold, or active lease on the blob in the Azure portal","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","If the blob is soft-deleted and unresolvable, use 'litestream reset <db>' to clear local state, or delete the blob explicitly in the portal"],"exampleFix":"// before\n_, err := c.client.DeleteBlob(ctx, c.Bucket, key, nil) // account lacks delete permission\n// after\n// fix credentials/SAS, then retry:\nerr := client.DeleteLTXFiles(ctx, fileInfos)\nif err != nil {\n\tif errors.Is(err, azureblob.ErrNoDeletePermission) { /* grant Storage Blob Data Contributor */ }\n\treturn err\n}","handlingStrategy":"try-catch","validationCode":"// Go\nfunc canDelete(ctx context.Context, c *abs.ReplicaClient) error {\n\t_, err := c.client.DeleteBlob(ctx, c.Bucket, \"__perm_probe__\", nil) // expect NotExists, not 403\n\tif err != nil && !isNotExists(err) { return err }\n\treturn nil\n}","typeGuard":"// Go\nfunc isPermissionError(err error) bool {\n\tvar re *azcore.ResponseError\n\treturn errors.As(err, &re) && re.StatusCode == http.StatusForbidden\n}","tryCatchPattern":"if err := client.DeleteLTXFiles(ctx, infos); err != nil {\n\tif isPermissionError(err) { /* fix credentials/role */ }\n\telse if transient(err) { /* retry with backoff — idempotent */ }\n\treturn err\n}","preventionTips":["Grant the identity Storage Blob Data Contributor (or SAS with delete permission)","Avoid immutability policies/legal holds on replica containers","Treat DeleteLTXFiles as retryable — it skips already-missing blobs","Monitor the wrapped Azure error codes in logs to distinguish 403 vs 5xx vs network"],"tags":["azure","storage","delete","network","permissions"],"backgroundTag":"blob-delete-failed","analyzedSha":"4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3","analyzedAt":"2026-09-06T18:29:25.564Z","contentChangedAt":"2026-09-06T18:29:25.564Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}