hashicorp/terraform · error

blob metadata was empty

Error message

blob metadata %q was empty

What it means

Returned by getLockInfo when the blob's metadata map has no terraformlockid value (raw == ""). This means the blob appears to be leased (getLockInfo was called from Lock/Unlock paths) but Terraform's lock metadata is missing — typically because the lease was acquired outside Terraform, writeLockInfo was interrupted, or the metadata was deleted manually.

Solutions

  1. Inspect the lease and metadata via `az storage blob show --account-name <acct> -c <container> -n <blob>`.
  2. Break the lease manually: `az storage blob lease break --account-name <acct> -c <container> -b <blob>`.
  3. After the lease is broken, retry terraform — a fresh lock will be written correctly.
  4. Avoid leasing the state blob outside terraform.
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: ensure the blob has terraformlockid metadata before trusting the lease.
func hasLockMetadata(ctx context.Context, acct, container, blob string) error {
    cmd := exec.CommandContext(ctx, "az", "storage", "blob", "show",
        "--account-name", acct, "-c", container, "-n", blob, "--query", "metadata.terraformlockid", "-o", "tsv")
    out, err := cmd.Output()
    if err != nil { return err }
    if strings.TrimSpace(string(out)) == "" { return fmt.Errorf("blob metadata missing terraformlockid") }
    return nil
}

Try / catch

// If getLockInfo fails on empty metadata, break the lease manually rather than relying on force-unlock.
_, err := client.Unlock(id)
if err != nil && strings.Contains(err.Error(), "blob metadata") {
    // break lease out-of-band
    log.Printf("lock metadata missing; break the lease manually: az storage blob lease break ...")
}

Prevention

When it happens

Trigger: (a) Someone acquired a lease via Azure portal / CLI / SDK directly on the state blob. (b) writeLockInfo partially completed — lease acquired but SetMetaData failed. (c) Metadata cleared out-of-band. (d) Blob was leased as part of an Azure backup / sync tool.

Common situations: Break-glass operation by an SRE using Azure CLI; partial lock from a terraform process killed between AcquireLease and writeLockInfo; third-party backup tooling that leases blobs.

Related errors


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

Appendix: source

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

	return info.ID, nil
}

func (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {
	options := blobs.GetPropertiesInput{}
	if c.leaseID != "" {
		options.LeaseID = &c.leaseID
	}

	ctx := newCtx()
	blob, err := c.giovanniBlobClient.GetProperties(ctx, c.containerName, c.keyName, options)
	if err != nil {
		return nil, err
	}

	raw := blob.MetaData[lockInfoMetaKey]
	if raw == "" {
		return nil, fmt.Errorf("blob metadata %q was empty", lockInfoMetaKey)
	}

	data, err := base64.StdEncoding.DecodeString(raw)
	if err != nil {
		return nil, err
	}

	lockInfo := &statemgr.LockInfo{}
	err = json.Unmarshal(data, lockInfo)
	if err != nil {
		return nil, err
	}

	return lockInfo, nil
}

// writes info to blob meta data, deletes metadata entry if info is nil
func (c *RemoteClient) writeLockInfo(info *statemgr.LockInfo) error {

View on GitHub (pinned to d32a084675)