opentofu/opentofu · error · statemgr.LockError

failed to delete lock info from metadata: %w

Error message

failed to delete lock info from metadata: %w

What it means

After verifying the ID, Unlock deletes terraformlockid via SetMetadata under the lease access condition (internal/backend/remote-state/azure/client.go:265); failure is wrapped in a LockError. The lease itself is still held (it is released in the next step), so the state stays locked but the metadata remains intact — a force-unlock retry with the same ID is viable.

Source

Thrown at internal/backend/remote-state/azure/client.go:265

func (c *RemoteClient) Unlock(ctx context.Context, id string) error {
	lockErr := &statemgr.LockError{}

	lockInfo, err := c.getLockInfo(ctx)
	if err != nil {
		lockErr.Err = fmt.Errorf("failed to retrieve lock info: %w", err)
		return lockErr
	}
	lockErr.Info = lockInfo

	if lockInfo.ID != id {
		lockErr.Err = fmt.Errorf("lock id %q does not match existing lock", id)
		return lockErr
	}

	c.setLeaseID(&lockInfo.ID)
	if err := c.writeLockInfo(ctx, nil); err != nil {
		lockErr.Err = fmt.Errorf("failed to delete lock info from metadata: %w", err)
		return lockErr
	}

	ctx, ctxCancel := c.getContextWithTimeout(ctx)
	defer ctxCancel()

	leaseOptions := &lease.BlobClientOptions{
		LeaseID: c.leaseID,
	}
	leaseClient, err := lease.NewBlobClient(c.blobClient, leaseOptions)
	if err != nil {
		lockErr.Err = fmt.Errorf("error getting blob lease client: %w", err)
		return lockErr
	}

	_, err = leaseClient.ReleaseLease(ctx, nil)
	if err != nil {
		lockErr.Err = fmt.Errorf("error when releasing lease for azure lock: %w", err)

View on GitHub (pinned to 3561785c48)

Solutions

  1. Retry `tofu force-unlock <same ID>` — the metadata still exists so the retry can complete
  2. On 403: grant write permission to the credential
  3. On persistent 412: break the lease manually with az CLI
  4. Confirm no other tooling touches the blob during unlocks

Example fix

// before
# Error: failed to delete lock info from metadata: ... (state still locked)

// after
tofu force-unlock 1e8eca5a-...  # retry; metadata intact so unlock can complete
# still failing -> break the lease:
az storage blob lease break --account-name st --container-name tfstate --blob-name prod.tfstate --auth-mode login
Defensive patterns

Strategy: retry

Type guard

func isLeasePrecondition(err error) bool {
    var re *azcore.ResponseError
    return errors.As(err, &re) && re.StatusCode == 412
}

Try / catch

err := client.Unlock(ctx, id)
if le, ok := asLockError(err); ok && strings.Contains(le.Err.Error(), "failed to delete lock info") {
    if isLeasePrecondition(le.Err) {
        // lease changed hands: metadata intact but retry will 412 again -> break lease manually
    } else {
        // transient (timeout/403 fixed): retry force-unlock with the same ID
    }
}

Prevention

When it happens

Trigger: 403 write denied on SetMetadata; 412 when the lease was broken or re-acquired externally between the ID check and the metadata write; CPK mismatch; context timeout.

Common situations: Credential without write permission; external lease interference mid-unlock; storage throttling.

Related errors


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