opentofu/opentofu · critical
Error unlocking Azure state. Lock ID: %s Error: %w You may
Error message
Error unlocking Azure state. Lock ID: %s Error: %w You may have to force-unlock this state in order to use it again.
What it means
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.
Source
Thrown at internal/backend/remote-state/azure/backend_state.go:96
// Grab the value
if err := stateMgr.RefreshState(context.TODO()); err != nil {
return nil, err
}
//if this isn't the default state name, we need to create the object so
//it's listed by States.
if v := stateMgr.State(); v == nil {
// take a lock on this state while we write it
lockInfo := statemgr.NewLockInfo()
lockInfo.Operation = "init"
lockId, err := client.Lock(context.TODO(), lockInfo)
if err != nil {
return nil, fmt.Errorf("failed to lock azure state: %w", err)
}
// Local helper function so we can call it multiple places
lockUnlock := func(parent error) error {
if err := stateMgr.Unlock(context.TODO(), lockId); err != nil {
return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
}
return parent
}
if err := stateMgr.WriteState(states.NewState()); err != nil {
err = lockUnlock(err)
return nil, err
}
if err := stateMgr.PersistState(context.TODO(), nil); err != nil {
err = lockUnlock(err)
return nil, err
}
// Unlock, the state should now be initialized
if err := lockUnlock(nil); err != nil {
return nil, err
}
}View on GitHub (pinned to 3561785c48)
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
Example fix
// before # Error unlocking Azure state. Lock ID: 1e8eca5a-... // after tofu force-unlock 1e8eca5a-... # if that errors, clear the dangling lease manually: az storage blob lease break --account-name sttfstate --container-name tfstate --blob-name prod.tfstate --auth-mode login tofu init
Defensive patterns
Strategy: try-catch
Validate before calling
// after StateMgr returns an error, check for a dangling lease
props, err := blobClient.GetProperties(ctx, nil)
if err == nil && props.LeaseStatus != nil && *props.LeaseStatus == lease.StatusTypeLocked {
// init failed mid-write with lock held: force-unlock or break lease before anything else
} Type guard
func asLockError(err error) (*statemgr.LockError, bool) {
var le *statemgr.LockError
if errors.As(err, &le) {
return le, true
}
return nil, false
} Try / catch
err := stateMgr.PersistState(ctx, nil)
if err != nil {
if unlockErr := stateMgr.Unlock(ctx, lockID); unlockErr != nil {
// escalate immediately: blob left leased with lockID; surface it for force-unlock
log.Fatalf("state left locked (%s): %v", lockID, unlockErr)
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- state blob is already locked
- blob metadata %q was empty
- error getting blob properties while writing lock: %w
- lock id %q does not match existing lock
- failed to delete lock info from metadata: %w
AI-assisted analysis of opentofu/opentofu@3561785c48 (2026-08-15).
Data as JSON: /api/errors/15c60afaa65bad12.
Report an issue: GitHub.