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

  1. Run `tofu force-unlock <Lock ID from the message>`
  2. 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>`
  3. Fix the underlying write failure (credentials, permissions, timeout) that started the cascade
  4. 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

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


AI-assisted analysis of opentofu/opentofu@3561785c48 (2026-08-15). Data as JSON: /api/errors/15c60afaa65bad12. Report an issue: GitHub.