hashicorp/terraform · error

error snapshotting Blob

Error message

error snapshotting Blob %q (Container %q / Account %q): %+v

What it means

Thrown by RemoteClient.Put when snapshot = true and c.giovanniBlobClient.Snapshot fails before overwriting the state blob. Snapshot creates a point-in-time copy of the existing blob; failure aborts the write to protect the prior state. Causes are RBAC (cannot snapshot), lease conflict, account in read-only/locked state, or snapshot restrictions on the SKU.

Solutions

  1. Ensure the identity has Storage Blob Data Contributor (or equivalent) on the account/container.
  2. Set snapshot = false in the backend if snapshots are not required.
  3. Release any conflicting leases before retrying.
  4. Remove an Azure resource lock on the storage account if it blocks snapshots.
  5. Check the storage account's soft-delete / WORM policy for snapshot conflicts.

Example fix

// before
terraform {
  backend "azurerm" {
    snapshot = true
  }
}
// after
terraform {
  backend "azurerm" {
    snapshot = false
  }
}
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: confirm the identity can snapshot blobs before enabling snapshot = true.
func canSnapshotBlob(ctx context.Context, acct, container, blob string) error {
    // attempt a snapshot on a throwaway test blob, or check role assignments
    cmd := exec.CommandContext(ctx, "az", "role", "assignment", "list", "--assignee", os.Getenv("ARM_CLIENT_ID"), "--role", "Storage Blob Data Contributor")
    if out, err := cmd.Output(); err != nil || len(out) == 0 {
        return fmt.Errorf("identity lacks Storage Blob Data Contributor; cannot snapshot")
    }
    return nil
}

Try / catch

// If snapshotting is non-critical, retry Put without it.
if diags.HasErrors() && snapshotEnabled {
    log.Printf("snapshot failed; retrying Put without snapshot")
    client.Snapshot = false
    diags = client.Put(data)
}

Prevention

When it happens

Trigger: (a) Identity lacks permission to create blob snapshots (Storage Blob Data Contributor needed). (b) Blob is currently leased by another client. (c) Account is read-only or has a deny-assignment. (d) Soft-deleted / under recovery. (e) Premium SKU snapshot restrictions.

Common situations: Using AAD auth without Storage Blob Data Contributor; concurrent apply holds the lease; storage account has a CanNotDelete lock; account under immutable-blob / WORM policy.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/750c91c43dec6138. Report an issue: GitHub.

Appendix: source

Thrown at internal/backend/remote-state/azure/client.go:102

	setOptions := blobs.SetPropertiesInput{}
	putOptions := blobs.PutBlockBlobInput{}

	options := blobs.GetInput{}
	if c.leaseID != "" {
		options.LeaseID = &c.leaseID
		getOptions.LeaseID = &c.leaseID
		setOptions.LeaseID = &c.leaseID
		putOptions.LeaseID = &c.leaseID
	}

	ctx := newCtx()

	if c.snapshot {
		snapshotInput := blobs.SnapshotInput{LeaseID: options.LeaseID}

		log.Printf("[DEBUG] Snapshotting existing Blob %q (Container %q / Account %q)", c.keyName, c.containerName, c.accountName)
		if _, err := c.giovanniBlobClient.Snapshot(ctx, c.containerName, c.keyName, snapshotInput); err != nil {
			return diags.Append(fmt.Errorf("error snapshotting Blob %q (Container %q / Account %q): %+v", c.keyName, c.containerName, c.accountName, err))
		}

		log.Print("[DEBUG] Created blob snapshot")
	}

	blob, err := c.giovanniBlobClient.GetProperties(ctx, c.containerName, c.keyName, getOptions)
	if err != nil {
		if !response.WasNotFound(blob.HttpResponse) {
			return diags.Append(err)
		}
	}

	contentType := "application/json"
	putOptions.Content = &data
	putOptions.ContentType = &contentType
	putOptions.MetaData = blob.MetaData
	_, err = c.giovanniBlobClient.PutBlockBlob(ctx, c.containerName, c.keyName, putOptions)

View on GitHub (pinned to d32a084675)