{"record":{"id":"d35639c495dec685","repo":"hashicorp/terraform","slug":"error-unlocking-azure-state-lock-id-s-error","errorCode":null,"errorMessage":"Error unlocking Azure state. Lock ID: %s\n\nError: %s\n\nYou may have to force-unlock this state in order to use it again.","messagePattern":"Error unlocking Azure state\\. Lock ID: (.+?)\n\nError: (.+?)\n\nYou may have to force-unlock this state in order to use it again\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/azure/backend_state.go","lineNumber":125,"sourceCode":"\t// Grab the value\n\tif err := stateMgr.RefreshState(); err != nil {\n\t\treturn nil, diags.Append(err)\n\t}\n\t//if this isn't the default state name, we need to create the object so\n\t//it's listed by States.\n\tif v := stateMgr.State(); v == nil {\n\t\t// take a lock on this state while we write it\n\t\tlockInfo := statemgr.NewLockInfo()\n\t\tlockInfo.Operation = \"init\"\n\t\tlockId, err := client.Lock(lockInfo)\n\t\tif err != nil {\n\t\t\treturn nil, diags.Append(fmt.Errorf(\"failed to lock azure state: %s\", err))\n\t\t}\n\n\t\t// Local helper function so we can call it multiple places\n\t\tlockUnlock := func(parent error) error {\n\t\t\tif err := stateMgr.Unlock(lockId); err != nil {\n\t\t\t\treturn fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)\n\t\t\t}\n\t\t\treturn parent\n\t\t}\n\n\t\t// Grab the value\n\t\tif err := stateMgr.RefreshState(); err != nil {\n\t\t\terr = lockUnlock(err)\n\t\t\treturn nil, diags.Append(err)\n\t\t}\n\t\t//if this isn't the default state name, we need to create the object so\n\t\t//it's listed by States.\n\t\tif v := stateMgr.State(); v == nil {\n\t\t\t// If we have no state, we have to create an empty state\n\t\t\tif err := stateMgr.WriteState(states.NewState()); err != nil {\n\t\t\t\terr = lockUnlock(err)\n\t\t\t\treturn nil, diags.Append(err)\n\t\t\t}\n\t\t\tif err := stateMgr.PersistState(nil); err != nil {","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/azure/backend_state.go#L107-L143","documentation":"During StateMgr init, after acquiring a lease to create a new state blob, if a subsequent step (RefreshState/WriteState/PersistState) fails the backend tries to release the lease via lockUnlock -> stateMgr.Unlock. If that Unlock itself fails, this formatted error (errStateUnlock constant, backend_state.go:170-176) is returned, telling the user the state is still locked and must be force-unlocked. It is a recovery-path failure that leaves state locked on purpose for safety.","triggerScenarios":"Produced at backend_state.go:123-127 inside the lockUnlock closure when stateMgr.Unlock(lockId) returns an error during the new-state initialization flow (the v == nil branch at line 113). The lease was acquired but the cleanup unlock failed.","commonSituations":"The write failed due to a data-plane error and then the unlock also hit a transient network error; the lease expired or was broken by another process between lock and unlock; the lock metadata write (SetMetaData) failed (error 158) making Unlock return a LockError.","solutions":["Run 'terraform force-unlock <lock-id>' using the Lock ID printed in the message to release the stuck lease","If force-unlock fails, break the lease in Azure: az storage blob lease break --account-name <account> -c <container> -b <key>","Re-run 'terraform init' then the original command after unlocking","Investigate the root write failure that triggered the unlock path"],"exampleFix":"# the message includes the Lock ID to force-unlock\nterraform force-unlock <Lock-ID-from-error>\n\n# then re-run\nterraform init\nterraform apply","handlingStrategy":"try-catch","validationCode":"# Detect a stuck lease from the failed-unlock flow before re-running\nLID=$(terraform force-unlock -force 2>&1 | grep -oE '[0-9a-f-]{36}' | head -1)\naz storage blob lease status --account-name \"$ARM_STORAGE_ACCOUNT_NAME\" -c \"$ARM_CONTAINER_NAME\" -n \"$ARM_KEY\" 2>/dev/null","typeGuard":null,"tryCatchPattern":"# The error embeds the Lock ID; capture and force-unlock, then break lease as fallback\nrecover_stuck_lock() {\n  LID=$(grep -oE 'Lock ID: [0-9a-f-]{36}' err.log | grep -oE '[0-9a-f-]{36}' | head -1)\n  terraform force-unlock -force \"$LID\" 2>/dev/null || \\\n    az storage blob lease break --account-name \"$ARM_STORAGE_ACCOUNT_NAME\" -c \"$ARM_CONTAINER_NAME\" -n \"$ARM_KEY\"\n}","preventionTips":["Always read the Lock ID from the message before force-unlocking","If force-unlock itself fails, break the lease at the Azure layer as the fallback","Investigate the original write failure that triggered the unlock path"],"tags":["azure","state","locking","lease","force-unlock","recovery"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T20:17:04.800Z"}