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 errView on GitHub (pinned to c9def3e214)
Solutions
- Wait for the other operation to finish and release the Lease, then retry.
- If the holder is stale (confirmed no active run), run 'terraform force-unlock <lock-id>' to delete the Lease.
- Manually delete the Lease object (kubectl delete lease <lock-tfstate-...>) only if you are certain no run is active.
- 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
- Detect statemgr.LockError and present the holder info + force-unlock ID to users.
- Coordinate CI pipelines so they don't run concurrent applies against the same workspace.
- Investigate crashed/killed terraform processes that leave orphaned Leases.
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.