hashicorp/terraform · error
Lease does does not have
Error message
Lease does does not have %q label
What it means
Returned by deleteLease when the targeted Lease lacks the tfstateKey label (client.go:396-400). Mirrors deleteSecret's guard: only Leases the backend created (and labeled) may be deleted. Note the same 'does does' typo as the Secret variant.
Solutions
- Verify the Lease is a Terraform lock Lease; if so, re-apply the tfstateKey=true label with kubectl label lease.
- If unrelated, resolve the naming collision rather than deleting.
- Audit admission webhooks/controllers for label stripping on the coordination.k8s.io namespace.
- The 'does does' typo is cosmetic — safe to ignore.
Defensive patterns
Strategy: validation
Validate before calling
// Before deleting, confirm the Lease is backend-managed:
// lease, _ := getLease(name)
// if lease.GetLabels()[tfstateKey] != "true" { /* not managed; refuse to delete */ } Try / catch
// if err := client.deleteLease(name); err != nil {
// if strings.Contains(err.Error(), "Lease does does not have") {
// // re-label if genuinely a state Lease, else investigate collision
// }
// } Prevention
- Preserve the tfstateKey=true label on backend-managed Leases.
- Exempt coordination Leases created by Terraform from label-stripping controllers.
- Avoid Lease name collisions with application Leases.
When it happens
Trigger: deleteLease is invoked on a Lease without the tfstateKey=true label — either manually created, label-stripped by a webhook, or a name collision with an unrelated coordination Lease.
Common situations: A mutation/label-stripping webhook removing labels; manual cleanup that removed labels; name collision with application Leases; cross-namespace label policy enforcement.
Related errors
- Secret does does not have
- state is already unlocked
- blob metadata was empty
- can't delete default state
- can't delete default state
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/a460fbbd74163a02.
Report an issue: GitHub.
Appendix: 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 d32a084675)