hashicorp/terraform · error · statemgr.LockError

lock id %q does not match existing lock

Error message

lock id %q does not match existing lock

What it means

Raised in RemoteClient.Unlock (client.go:282-284). For safety, Unlock compares the lock ID passed in against the ID stored in the blob lock metadata; if they differ, it refuses to release someone else's lease and returns a LockError. This prevents one Terraform run from unlocking state held by a different run.

Source

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

		MetaData: blob.MetaData,
	}

	_, err = c.giovanniBlobClient.SetMetaData(ctx, c.containerName, c.keyName, opts)
	return err
}

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

	lockInfo, err := c.getLockInfo()
	if err != nil {
		lockErr.Err = fmt.Errorf("failed to retrieve lock info: %s", 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.leaseID = lockInfo.ID
	if err := c.writeLockInfo(nil); err != nil {
		lockErr.Err = fmt.Errorf("failed to delete lock info from metadata: %s", err)
		return lockErr
	}

	ctx := newCtx()
	_, err = c.giovanniBlobClient.ReleaseLease(ctx, c.containerName, c.keyName, blobs.ReleaseLeaseInput{LeaseID: id})
	if err != nil {
		lockErr.Err = err
		return lockErr
	}

	c.leaseID = ""

View on GitHub (pinned to c9def3e214)

Solutions

  1. Use the correct lock ID shown by the current 'terraform' error or by querying the blob lock metadata
  2. If you are certain the lock is stale, break the lease directly: az storage blob lease break --account-name <account> -c <container> -b <key>
  3. Inspect current lock info: az storage blob metadata show / az storage blob lease show

Example fix

# before: wrong/stale lock id
terraform force-unlock 00000000-0000-0000-0000-000000000000
# -> 'lock id does not match existing lock'

# after: read the actual stored lock id from metadata, or break the lease
az storage blob lease break \
  --account-name mystage --auth-mode login \
  -c tfstate -b "prod.terraform.tfstate"
terraform init
Defensive patterns

Strategy: validation

Validate before calling

# Validate the lock id matches the stored metadata before force-unlock
STORED=$(az storage blob metadata show --account-name "$ARM_STORAGE_ACCOUNT_NAME" -c "$ARM_CONTAINER_NAME" -n "$ARM_KEY" --query 'terraformlockid' -o tsv 2>/dev/null | base64 -d 2>/dev/null | grep -oE '"ID":"[0-9a-f-]{36}"' | grep -oE '[0-9a-f-]{36}')
[ "$STORED" = "$1" ] && echo "OK: id matches" || echo "FAIL: id mismatch (157); stored=$STORED supplied=$1"
# usage: validate_lock_id "$PROPOSED_LOCK_ID"

Prevention

When it happens

Trigger: Produced at client.go:282-283 when lockInfo.ID != id during Unlock. Happens when a stale or wrong lock ID is passed to 'terraform force-unlock', or when Terraform's recorded lock ID no longer matches the actual lease holder.

Common situations: Running 'terraform force-unlock' with the wrong ID (a typo, or an old ID from a previous run); two runs interfering; the lease ID changed because someone already broke/reacquired the lease.

Related errors


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