{"record":{"id":"15c60afaa65bad12","repo":"opentofu/opentofu","slug":"error-unlocking-azure-state-lock-id-s-error","errorCode":null,"errorMessage":"Error unlocking Azure state. Lock ID: %s\n\nError: %w\n\nYou may have to force-unlock this state in order to use it again.","messagePattern":"Error unlocking Azure state\\. Lock ID: (.+?)\n\nError: %w\n\nYou may have to force-unlock this state in order to use it again\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/backend/remote-state/azure/backend_state.go","lineNumber":96,"sourceCode":"\t// Grab the value\n\tif err := stateMgr.RefreshState(context.TODO()); err != nil {\n\t\treturn nil, 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(context.TODO(), lockInfo)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to lock azure state: %w\", 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(context.TODO(), 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\tif err := stateMgr.WriteState(states.NewState()); err != nil {\n\t\t\terr = lockUnlock(err)\n\t\t\treturn nil, err\n\t\t}\n\t\tif err := stateMgr.PersistState(context.TODO(), nil); err != nil {\n\t\t\terr = lockUnlock(err)\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// Unlock, the state should now be initialized\n\t\tif err := lockUnlock(nil); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/opentofu/opentofu/blob/3561785c48c1ce615e7c50261bd351f26053efa2/internal/backend/remote-state/azure/backend_state.go#L78-L114","documentation":"Emitted by the lockUnlock helper in Backend.StateMgr (format string errStateUnlock, internal/backend/remote-state/azure/backend_state.go:127, used at line 96). During first-time state creation, after client.Lock succeeds, a failure in WriteState or PersistState triggers stateMgr.Unlock; when that unlock also fails, this composite message with the Lock ID is returned. The blob is left holding an infinite lease with no live owner, blocking all future operations until force-unlock or a manual lease break.","triggerScenarios":"The state write path fails (e.g. blob upload 403/timeout, which is error 147) and the subsequent Unlock/ReleaseLease also fails: credentials expired mid-run, network partition between upload and lease release, 412 LeaseIdMismatch because the lease was broken or re-acquired externally, or ARM_TIMEOUT_SECONDS deadline reached during unlock.","commonSituations":"Storage key rotated while a long init/apply was running; flaky network hitting exactly between write and unlock; an operator manually broke or re-leased the blob mid-run; AzureAD token expiry (use_azuread_auth) during a large state upload.","solutions":["Run `tofu force-unlock <Lock ID from the message>`","If force-unlock fails (e.g. lock metadata empty), break the lease directly: `az storage blob lease break --account-name <acct> --container-name <cont> --blob-name <key>`","Fix the underlying write failure (credentials, permissions, timeout) that started the cascade","Re-run init and confirm the workspace functions"],"exampleFix":"// before\n# Error unlocking Azure state. Lock ID: 1e8eca5a-...\n\n// after\ntofu force-unlock 1e8eca5a-...\n# if that errors, clear the dangling lease manually:\naz storage blob lease break --account-name sttfstate --container-name tfstate --blob-name prod.tfstate --auth-mode login\ntofu init","handlingStrategy":"try-catch","validationCode":"// after StateMgr returns an error, check for a dangling lease\nprops, err := blobClient.GetProperties(ctx, nil)\nif err == nil && props.LeaseStatus != nil && *props.LeaseStatus == lease.StatusTypeLocked {\n    // init failed mid-write with lock held: force-unlock or break lease before anything else\n}","typeGuard":"func asLockError(err error) (*statemgr.LockError, bool) {\n    var le *statemgr.LockError\n    if errors.As(err, &le) {\n        return le, true\n    }\n    return nil, false\n}","tryCatchPattern":"err := stateMgr.PersistState(ctx, nil)\nif err != nil {\n    if unlockErr := stateMgr.Unlock(ctx, lockID); unlockErr != nil {\n        // escalate immediately: blob left leased with lockID; surface it for force-unlock\n        log.Fatalf(\"state left locked (%s): %v\", lockID, unlockErr)\n    }\n    return err\n}","preventionTips":["Use stable credentials (account key or long-lived SAS) for runs that upload large states","Size ARM_TIMEOUT_SECONDS to your largest expected state write plus the unlock round-trip","Monitor state blobs for LeaseState=leased after failed runs and clear them promptly","Do not manually break or re-acquire leases while a run is active"],"tags":["azure","blob-storage","state-lock","force-unlock","lease","go"],"backgroundTag":null,"analyzedSha":"3561785c48c1ce615e7c50261bd351f26053efa2","analyzedAt":"2026-08-15T23:27:16.226Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}