hashicorp/terraform · error

failed to lock azure state

Error message

failed to lock azure state: %s

What it means

Thrown by StateMgr when a workspace state object does not yet exist and the backend tries to acquire a lease-based lock to initialize it, but client.Lock returns an error. Lock fails when another client already holds the lease, when a stale lease from a crashed run remains, or when the lock-info metadata write fails.

Solutions

  1. Run `terraform force-unlock <lock-id>` using the ID printed in the LockError info.
  2. If the other run is legitimate, wait for it to finish then retry.
  3. Inspect the lease via Azure portal or `az storage blob lease list` / `show`.
  4. If the lock info metadata is corrupt, break the lease manually via `az storage blob lease break`.
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: check whether the state blob is already leased before attempting to lock.
func isStateBlobLeased(ctx context.Context, acct, container, blob string) (bool, error) {
    cmd := exec.CommandContext(ctx, "az", "storage", "blob", "show",
        "--account-name", acct, "-c", container, "-n", blob, "--query", "properties.lease.status", "-o", "tsv")
    out, err := cmd.Output()
    if err != nil { return false, err }
    return strings.TrimSpace(string(out)) == "locked", nil
}

Try / catch

// On a lock failure, parse the LockError and offer the lock id for force-unlock.
stateMgr, diags := backend.StateMgr(name)
if diags.HasErrors() {
    var le *statemgr.LockError
    if errors.As(diags.Err(), &le) && le.Info != nil {
        log.Printf("state is locked by %s; run: terraform force-unlock %s", le.Info.Operation, le.Info.ID)
    }
}

Prevention

When it happens

Trigger: (a) Another terraform process already acquired the lease on the same state blob. (b) A prior run crashed without releasing (stale lease). (c) writeLockInfo failed (SetMetaData network/RBAC error). (d) AcquireLease itself failed (throttling).

Common situations: Concurrent `terraform apply` in two terminals against the same workspace; CI overlap; crashed run left a lease; long-held lock from interrupted operation.

Related errors


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

Appendix: source

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

		accountName:        b.accountName,
		snapshot:           b.snapshot,
	}

	stateMgr := &remote.State{Client: client}

	// 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 {

View on GitHub (pinned to d32a084675)