{"record":{"id":"c3ece8f65d3172e3","repo":"opentofu/opentofu","slug":"failed-to-delete-lock-info-from-metadata-w","errorCode":null,"errorMessage":"failed to delete lock info from metadata: %w","messagePattern":"failed to delete lock info from metadata: %w","errorType":"exception","errorClass":"statemgr.LockError","httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/azure/client.go","lineNumber":265,"sourceCode":"\nfunc (c *RemoteClient) Unlock(ctx context.Context, id string) error {\n\tlockErr := &statemgr.LockError{}\n\n\tlockInfo, err := c.getLockInfo(ctx)\n\tif err != nil {\n\t\tlockErr.Err = fmt.Errorf(\"failed to retrieve lock info: %w\", err)\n\t\treturn lockErr\n\t}\n\tlockErr.Info = lockInfo\n\n\tif lockInfo.ID != id {\n\t\tlockErr.Err = fmt.Errorf(\"lock id %q does not match existing lock\", id)\n\t\treturn lockErr\n\t}\n\n\tc.setLeaseID(&lockInfo.ID)\n\tif err := c.writeLockInfo(ctx, nil); err != nil {\n\t\tlockErr.Err = fmt.Errorf(\"failed to delete lock info from metadata: %w\", err)\n\t\treturn lockErr\n\t}\n\n\tctx, ctxCancel := c.getContextWithTimeout(ctx)\n\tdefer ctxCancel()\n\n\tleaseOptions := &lease.BlobClientOptions{\n\t\tLeaseID: c.leaseID,\n\t}\n\tleaseClient, err := lease.NewBlobClient(c.blobClient, leaseOptions)\n\tif err != nil {\n\t\tlockErr.Err = fmt.Errorf(\"error getting blob lease client: %w\", err)\n\t\treturn lockErr\n\t}\n\n\t_, err = leaseClient.ReleaseLease(ctx, nil)\n\tif err != nil {\n\t\tlockErr.Err = fmt.Errorf(\"error when releasing lease for azure lock: %w\", err)","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/opentofu/opentofu/blob/3561785c48c1ce615e7c50261bd351f26053efa2/internal/backend/remote-state/azure/client.go#L247-L283","documentation":"After verifying the ID, Unlock deletes terraformlockid via SetMetadata under the lease access condition (internal/backend/remote-state/azure/client.go:265); failure is wrapped in a LockError. The lease itself is still held (it is released in the next step), so the state stays locked but the metadata remains intact — a force-unlock retry with the same ID is viable.","triggerScenarios":"403 write denied on SetMetadata; 412 when the lease was broken or re-acquired externally between the ID check and the metadata write; CPK mismatch; context timeout.","commonSituations":"Credential without write permission; external lease interference mid-unlock; storage throttling.","solutions":["Retry `tofu force-unlock <same ID>` — the metadata still exists so the retry can complete","On 403: grant write permission to the credential","On persistent 412: break the lease manually with az CLI","Confirm no other tooling touches the blob during unlocks"],"exampleFix":"// before\n# Error: failed to delete lock info from metadata: ... (state still locked)\n\n// after\ntofu force-unlock 1e8eca5a-...  # retry; metadata intact so unlock can complete\n# still failing -> break the lease:\naz storage blob lease break --account-name st --container-name tfstate --blob-name prod.tfstate --auth-mode login","handlingStrategy":"retry","validationCode":null,"typeGuard":"func isLeasePrecondition(err error) bool {\n    var re *azcore.ResponseError\n    return errors.As(err, &re) && re.StatusCode == 412\n}","tryCatchPattern":"err := client.Unlock(ctx, id)\nif le, ok := asLockError(err); ok && strings.Contains(le.Err.Error(), \"failed to delete lock info\") {\n    if isLeasePrecondition(le.Err) {\n        // lease changed hands: metadata intact but retry will 412 again -> break lease manually\n    } else {\n        // transient (timeout/403 fixed): retry force-unlock with the same ID\n    }\n}","preventionTips":["Retry force-unlock with the same ID once the transient cause (timeout, 403) is fixed — metadata is still intact","Ensure write permission before attempting unlock, not just read","Prevent external lease interference while unlocks are in flight","Escalate to manual break-lease only after a retried force-unlock fails with 412"],"tags":["azure","blob-storage","state-lock","unlock","metadata","lease","go"],"backgroundTag":null,"analyzedSha":"3561785c48c1ce615e7c50261bd351f26053efa2","analyzedAt":"2026-08-15T23:27:16.226Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}