hashicorp/terraform · error

Secret does does not have %q label

Error message

Secret does does not have %q label

What it means

deleteSecret() refuses to delete a Secret that lacks the backend's ownership label `tfstate=true`. It first GETs the secret, checks labels[tfstateKey]; if absent or not 'true' it aborts with this error (note the duplicated 'does does' wording in the source). This guard prevents deleting arbitrary user Secrets whose name happens to match the computed secret name.

Source

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

func (c *RemoteClient) getSecret(name string) (*unstructured.Unstructured, error) {
	return c.kubernetesSecretClient.Get(context.Background(), name, metav1.GetOptions{})
}

func (c *RemoteClient) getLease(name string) (*coordinationv1.Lease, error) {
	return c.kubernetesLeaseClient.Get(context.Background(), name, metav1.GetOptions{})
}

func (c *RemoteClient) deleteSecret(name string) error {
	secret, err := c.getSecret(name)
	if err != nil {
		return err
	}

	labels := secret.GetLabels()
	v, ok := labels[tfstateKey]
	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)
	}

View on GitHub (pinned to c9def3e214)

Solutions

  1. Confirm the Secret is not a real state object; if it is legitimate but untagged, add the `tfstate=true` label before retrying deletion.
  2. If it is an unrelated Secret, rename it or rename your secret_suffix/namespace so names do not collide.
  3. Manually delete the offending Secret with kubectl if it is safe to do so, then re-run.

Example fix

# before - a colliding non-state Secret blocks workspace deletion
terraform workspace delete myapp   # error: Secret does not have tfstate label

# after - tag the legitimate state Secret (or remove the colliding one)
kubectl label secret tfstate-default-myapp tfstate=true -n default
terraform workspace delete myapp
Defensive patterns

Strategy: validation

Validate before calling

// Verify the secret carries the ownership label before attempting deletion
secret, err := c.kubernetesSecretClient.Get(ctx, name, metav1.GetOptions{})
if err != nil { return err }
if v, ok := secret.GetLabels()["tfstate"]; !ok || v != "true" {
    // not a backend-owned secret; do not delete via the backend
    return nil
}
return c.kubernetesSecretClient.Delete(ctx, name, metav1.DeleteOptions{})

Type guard

func isBackendOwnedSecret(obj *unstructured.Unstructured) bool {
    v, ok := obj.GetLabels()["tfstate"]
    return ok && v == "true"
}

Prevention

When it happens

Trigger: Delete()/DeleteWorkspace flow calling deleteSecret(name) at client.go:373-388 against a Secret that exists but has no `tfstate=true` label, e.g. a manually created Secret or one created by an older backend version without the label.

Common situations: A pre-existing Secret with a colliding name (e.g. someone created 'tfstate-default-myapp' manually); state created by an older Terraform version that did not tag secrets with the tfstate label; manual label removal.

Related errors


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