hashicorp/terraform · error

Error unlocking Azure state. Lock ID

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

Produced by the lockUnlock helper in StateMgr when stateMgr.Unlock(lockId) fails during cleanup of the initialization flow. Indicates the lease could not be released after a partial write/refresh failure. The message instructs the user that a manual force-unlock may be required because the lease may still be held.

Solutions

  1. Run `terraform force-unlock <lock-id>` with the ID printed in the message.
  2. Verify the lease is gone via Azure portal / `az storage blob lease show`.
  3. If the blob was deleted, the unlock is moot — recreate the workspace state.
  4. Retry the original terraform command once the lock is cleared.
Defensive patterns

Strategy: try-catch

Try / catch

// After a failed init that may have left a lease, attempt an explicit force-unlock.
if err := stateMgr.Unlock(lockId); err != nil {
    log.Printf("%s; capture lock id %s for manual force-unlock", err, lockId)
    // surface lockId to the operator / CI log
}

Prevention

When it happens

Trigger: (a) Network/transport failure during the ReleaseLease call. (b) Lease ID was already released or expired. (c) Permissions revoked mid-run. (d) State blob was deleted out-of-band.

Common situations: Flaky network mid-init; very long init that exceeded lease validity (rare since this backend uses infinite-duration leases); operator manually broke the lease during a run.

Related errors


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

Appendix: 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 d32a084675)