{"record":{"id":"ec73d9e0118f673c","repo":"hashicorp/terraform","slug":"blob-metadata-q-was-empty","errorCode":null,"errorMessage":"blob metadata %q was empty","messagePattern":"blob metadata %q was empty","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/azure/client.go","lineNumber":228,"sourceCode":"\n\treturn info.ID, nil\n}\n\nfunc (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {\n\toptions := blobs.GetPropertiesInput{}\n\tif c.leaseID != \"\" {\n\t\toptions.LeaseID = &c.leaseID\n\t}\n\n\tctx := newCtx()\n\tblob, err := c.giovanniBlobClient.GetProperties(ctx, c.containerName, c.keyName, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\traw := blob.MetaData[lockInfoMetaKey]\n\tif raw == \"\" {\n\t\treturn nil, fmt.Errorf(\"blob metadata %q was empty\", lockInfoMetaKey)\n\t}\n\n\tdata, err := base64.StdEncoding.DecodeString(raw)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlockInfo := &statemgr.LockInfo{}\n\terr = json.Unmarshal(data, lockInfo)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn lockInfo, nil\n}\n\n// writes info to blob meta data, deletes metadata entry if info is nil\nfunc (c *RemoteClient) writeLockInfo(info *statemgr.LockInfo) error {","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/azure/client.go#L210-L246","documentation":"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.","triggerScenarios":"(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.","commonSituations":"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.","solutions":["Inspect the lease and metadata via `az storage blob show --account-name <acct> -c <container> -n <blob>`.","Break the lease manually: `az storage blob lease break --account-name <acct> -c <container> -b <blob>`.","After the lease is broken, retry terraform — a fresh lock will be written correctly.","Avoid leasing the state blob outside terraform."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Pre-flight: ensure the blob has terraformlockid metadata before trusting the lease.\nfunc hasLockMetadata(ctx context.Context, acct, container, blob string) error {\n    cmd := exec.CommandContext(ctx, \"az\", \"storage\", \"blob\", \"show\",\n        \"--account-name\", acct, \"-c\", container, \"-n\", blob, \"--query\", \"metadata.terraformlockid\", \"-o\", \"tsv\")\n    out, err := cmd.Output()\n    if err != nil { return err }\n    if strings.TrimSpace(string(out)) == \"\" { return fmt.Errorf(\"blob metadata missing terraformlockid\") }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"// If getLockInfo fails on empty metadata, break the lease manually rather than relying on force-unlock.\n_, err := client.Unlock(id)\nif err != nil && strings.Contains(err.Error(), \"blob metadata\") {\n    // break lease out-of-band\n    log.Printf(\"lock metadata missing; break the lease manually: az storage blob lease break ...\")\n}","preventionTips":["Never lease the state blob outside terraform.","If a run crashes mid-Lock, immediately inspect metadata and break the lease if absent.","Periodically audit the state blob's lease/metadata via `az storage blob show`."],"tags":["azure","state-locking","lease","metadata","force-unlock"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}