hashicorp/terraform · error

Error unlocking Azure state. Lock ID: %s Error: %s You may

Error message

Error unlocking Azure state. Lock ID: %s

Error: %s

You may have to force-unlock this state in order to use it again.

What it means

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.

Source

Thrown at internal/backend/remote-state/azure/backend_state.go:125

	// Grab the value
	if err := stateMgr.RefreshState(); err != nil {
		return nil, diags.Append(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(lockInfo)
		if err != nil {
			return nil, diags.Append(fmt.Errorf("failed to lock azure state: %s", err))
		}

		// Local helper function so we can call it multiple places
		lockUnlock := func(parent error) error {
			if err := stateMgr.Unlock(lockId); err != nil {
				return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
			}
			return parent
		}

		// Grab the value
		if err := stateMgr.RefreshState(); err != nil {
			err = lockUnlock(err)
			return nil, diags.Append(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 {
			// If we have no state, we have to create an empty state
			if err := stateMgr.WriteState(states.NewState()); err != nil {
				err = lockUnlock(err)
				return nil, diags.Append(err)
			}
			if err := stateMgr.PersistState(nil); err != nil {

View on GitHub (pinned to c9def3e214)

Solutions

  1. Run 'terraform force-unlock <lock-id>' using the Lock ID printed in the message to release the stuck lease
  2. If force-unlock fails, break the lease in Azure: az storage blob lease break --account-name <account> -c <container> -b <key>
  3. Re-run 'terraform init' then the original command after unlocking
  4. Investigate the root write failure that triggered the unlock path

Example fix

# the message includes the Lock ID to force-unlock
terraform force-unlock <Lock-ID-from-error>

# then re-run
terraform init
terraform apply
Defensive patterns

Strategy: try-catch

Validate before calling

# Detect a stuck lease from the failed-unlock flow before re-running
LID=$(terraform force-unlock -force 2>&1 | grep -oE '[0-9a-f-]{36}' | head -1)
az storage blob lease status --account-name "$ARM_STORAGE_ACCOUNT_NAME" -c "$ARM_CONTAINER_NAME" -n "$ARM_KEY" 2>/dev/null

Try / catch

# The error embeds the Lock ID; capture and force-unlock, then break lease as fallback
recover_stuck_lock() {
  LID=$(grep -oE 'Lock ID: [0-9a-f-]{36}' err.log | grep -oE '[0-9a-f-]{36}' | head -1)
  terraform force-unlock -force "$LID" 2>/dev/null || \
    az storage blob lease break --account-name "$ARM_STORAGE_ACCOUNT_NAME" -c "$ARM_CONTAINER_NAME" -n "$ARM_KEY"
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/d35639c495dec685. Report an issue: GitHub.