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

  1. 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.
  2. If the Lease belongs to another system, change secret_suffix/namespace so names no longer collide.
  3. 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

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


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