hashicorp/terraform · error · LockError

the state is already locked by another terraform client

Error message

the state is already locked by another terraform client

What it means

Returned by RemoteClient.Lock (internal/backend/remote-state/kubernetes/client.go:285) wrapped in a statemgr.LockError when a coordination.k8s.io Lease already exists for the state and its HolderIdentity differs from info.ID. The k8s backend implements locking via a Lease per workspace; a held lease with a different holder means another terraform client owns the lock.

Source

Thrown at internal/backend/remote-state/kubernetes/client.go:285

			return "", err
		} else {
			return info.ID, nil
		}
	}

	if lease.Spec.HolderIdentity != nil {
		if *lease.Spec.HolderIdentity == info.ID {
			return info.ID, nil
		}

		currentLockInfo, err := c.getLockInfo(lease)
		if err != nil {
			return "", err
		}

		lockErr := &statemgr.LockError{
			Info: currentLockInfo,
			Err:  errors.New("the state is already locked by another terraform client"),
		}
		return "", lockErr
	}

	lease.Spec.HolderIdentity = pointer.StringPtr(info.ID)
	setLockInfo(lease, info.Marshal())
	_, err = c.kubernetesLeaseClient.Update(ctx, lease, metav1.UpdateOptions{})
	if err != nil {
		return "", err
	}

	return info.ID, err
}

func (c *RemoteClient) Unlock(id string) error {
	leaseName, err := c.createLeaseName()
	if err != nil {
		return err

View on GitHub (pinned to c9def3e214)

Solutions

  1. Wait for the other operation to finish and release the Lease, then retry.
  2. If the holder is stale (confirmed no active run), run 'terraform force-unlock <lock-id>' to delete the Lease.
  3. Manually delete the Lease object (kubectl delete lease <lock-tfstate-...>) only if you are certain no run is active.
  4. Investigate why the previous terraform exited without unlocking (OOM-kill, SIGKILL).
Defensive patterns

Strategy: try-catch

Try / catch

lockID, err := client.Lock(info)
if err != nil {
    var le *statemgr.LockError
    if errors.As(err, &le) {
        return fmt.Errorf("state already locked by %s (op %s) at %s; run 'terraform force-unlock %s' if stale",
            le.Info.Who, le.Info.Operation, le.Info.Created, le.Info.ID)
    }
    return err
}

Prevention

When it happens

Trigger: A second 'terraform apply'/'plan' runs while the first still holds the Lease; a previous terraform process crashed without releasing the Lease (stale holder); the same process re-locking with a new random info.ID after losing track of the old one.

Common situations: CI runners sharing a namespace and workspace where one pipeline hangs; a crashed/killed terraform leaving an orphaned Lease; manual edits to the Lease; concurrent developers hitting the same k8s state backend.

Related errors


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