hashicorp/terraform · error
Lease does does not have %q label
Error message
Lease does does not have %q label
What it means
deleteLease() refuses to delete a Lease that lacks the `tfstate=true` ownership label, mirroring the Secret guard. It GETs the lease, checks labels[tfstateKey]; if missing or not 'true' it aborts with this error (note the duplicated 'does does' wording). This protects against deleting Leases not owned by the backend.
Source
Thrown at internal/backend/remote-state/kubernetes/client.go:399
if !ok || v != "true" {
return fmt.Errorf("Secret does does not have %q label", tfstateKey)
}
delProp := metav1.DeletePropagationBackground
delOps := metav1.DeleteOptions{PropagationPolicy: &delProp}
return c.kubernetesSecretClient.Delete(context.Background(), name, delOps)
}
func (c *RemoteClient) deleteLease(name string) error {
secret, err := c.getLease(name)
if err != nil {
return err
}
labels := secret.GetLabels()
v, ok := labels[tfstateKey]
if !ok || v != "true" {
return fmt.Errorf("Lease does does not have %q label", tfstateKey)
}
delProp := metav1.DeletePropagationBackground
delOps := metav1.DeleteOptions{PropagationPolicy: &delProp}
return c.kubernetesLeaseClient.Delete(context.Background(), name, delOps)
}
func (c *RemoteClient) createSecretName(idx int) (string, error) {
secretName := strings.Join([]string{tfstateKey, c.workspace, c.nameSuffix}, "-")
if idx > 0 {
secretName = fmt.Sprintf("%s-part-%d", secretName, idx)
}
errs := validation.IsDNS1123Subdomain(secretName)
if len(errs) > 0 {
k8sInfo := `
This is a requirement for Kubernetes secret names. View on GitHub (pinned to c9def3e214)
Solutions
- Verify the Lease is actually a Terraform lock before touching it; if it is a leftover state lock, add the `tfstate=true` label or delete it manually with kubectl.
- If the Lease belongs to another system, change secret_suffix/namespace so names no longer collide.
- Re-run the deletion once the colliding/mislabeled Lease is resolved.
Example fix
# before - leftover untagged lease blocks state cleanup terraform workspace delete myapp # error: Lease does not have tfstate label # after - tag the legitimate lock lease (or delete it if stale) kubectl label lease lock-tfstate-default-myapp tfstate=true -n default terraform workspace delete myapp
Defensive patterns
Strategy: validation
Validate before calling
// Verify the lease carries the ownership label before deletion
lease, err := c.kubernetesLeaseClient.Get(ctx, name, metav1.GetOptions{})
if err != nil { return err }
if v, ok := lease.GetLabels()["tfstate"]; !ok || v != "true" {
// not a backend-owned lease; skip
return nil
}
return c.kubernetesLeaseClient.Delete(ctx, name, metav1.DeleteOptions{}) Type guard
func isBackendOwnedLease(l *coordinationv1.Lease) bool {
v, ok := l.GetLabels()["tfstate"]
return ok && v == "true"
} Prevention
- Avoid reusing the lock-tfstate-<ws>-<suffix> name for non-Terraform leases.
- Clean up stale lock leases during backend migrations.
- Keep secret_suffix stable so lease names do not collide across reinstalls.
When it happens
Trigger: Delete() calling deleteLease(name) at client.go:390-405 against a Lease that exists but has no `tfstate=true` label - e.g. a lease of the same name created by another controller or an older backend version.
Common situations: A colliding Lease created by another application (Leases are shared objects in coordination.k8s.io); a lease left by an older Terraform that did not label its locks; manual lease creation with the computed lock name.
Related errors
- Secret does does not have %q label
- Failed to configure: %s
- secret_suffix must not end with '-<number>', got %q
- Failed to initialize kubernetes configuration: %s
- can't delete default state
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/a460fbbd74163a02.
Report an issue: GitHub.