descricao: “Learn why Helm 3.14 on AKS 1.29 throws “cannot re-use a name that is still in use” and how to fix release name collisions caused by stricter CRD handling.”
I hit this exact error last month while trying to push a minor chart bump through our CI pipeline on an Azure Kubernetes Service (AKS) cluster that had just been upgraded to Kubernetes 1.29. The pipeline was using Helm 3.14, and the helm upgrade command blew up with:
Error: cannot re-use a name that is still in use
At first glance it looked like a classic release‑name collision, but everything else in the cluster was clean. The root cause turned out to be Helm 3.14’s new, stricter handling of CustomResourceDefinitions (CRDs) combined with how AKS 1.29 stores CRDs in the kube-system namespace. In this post I’ll walk through the exact conditions that trigger the failure, why Helm behaves that way, and three battle‑tested ways I use to get my upgrades back on track.
What changed in Helm 3.14 that makes AKS 1.29 upgrades explode?
Helm has always treated CRDs as “install‑once, never‑touch‑again” objects. Starting with Helm 3.14, the team added a safety net: if a chart tries to install a CRD that already exists and the existing CRD’s metadata.generation does not match the chart’s version, Helm aborts the whole release and reports a name‑reuse error. The intention is to prevent silent overwrites of production‑critical schemas.
AKS 1.29 introduced a subtle change in the way it registers built‑in CRDs for Azure‑specific extensions (e.g., aadpodidentitybindings, ingressclassparams). Those CRDs are now owned by the kube-system namespace and carry an annotation helm.sh/resource-policy: keep. When Helm 3.14 sees those CRDs during an upgrade, it thinks the chart is trying to re‑install a CRD that is already present, even though the chart itself never declares the CRD – the cluster does.
The combination of:
- Helm 3.14’s stricter CRD version check
- AKS 1.29’s built‑in CRDs being “owned” by the cluster
- A release that still has the old CRD version recorded in its secret
creates the exact error message you’re seeing.
Pro Tip: Run
helm version --shortandkubectl version --shortside‑by‑side before you start debugging. Knowing the exact Helm and Kubernetes versions saves you from chasing phantom bugs.
How can I reproduce the failure locally?
Reproducing the issue on a local KIND cluster is straightforward and helps you experiment without touching production. Here’s a minimal reproducible scenario:
# 1. Create a KIND cluster with Kubernetes 1.29 (requires KIND v0.23+)
kind create cluster --image kindest/node:v1.29.0
# 2. Install Helm 3.14
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version
# 3. Scaffold a chart that includes a CRD (e.g., a simple Foo CRD)
helm create myapp
cat <<'EOF' > myapp/crds/foo.example.com_foos.yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: foos.example.com
spec:
group: example.com
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
scope: Namespaced
names:
plural: foos
singular: foo
kind: Foo
EOF
# 4. Install the chart
helm install myapp ./myapp
# 5. Simulate AKS built‑in CRD by manually adding an annotation
kubectl annotate crd foos.example.com helm.sh/resource-policy=keep
# 6. Try to upgrade – this will hit the error
helm upgrade myapp ./myapp
The last command fails with the exact same error because Helm now sees the CRD it just installed (which now has the keep annotation) and refuses to “reuse” the name.
Why does the error say “cannot re‑use a name that is still in use”?
The wording is inherited from Helm’s internal releaseutil.ErrReleaseExists error, which historically covered two scenarios:
- Release name collision – two releases trying to share the same name.
- Resource name collision – a chart trying to create a resource that already exists and is owned by a different release.
In Helm 3.14 the CRD path was folded into the second case, but the error string was not updated. That’s why the message feels misleading when the real culprit is a CRD version mismatch.
What are the safe ways to get past the block?
I’ve settled on three approaches that work in production, each with its own trade‑offs. Choose the one that matches your risk tolerance and CI/CD constraints.
1️⃣ Delete the offending CRD from the release secret and let Helm treat it as a fresh install
Helm stores the entire manifest of a release in a secret named sh.helm.release.v1.<release-name>.v<N>. Inside that secret you’ll find the CRD’s raw YAML. By stripping out the CRD entry you convince Helm that the release never owned the CRD, so the upgrade proceeds.
Broken version (what you might try first):
# Attempting to delete the secret outright – catastrophic!
kubectl delete secret sh.helm.release.v1.myapp.v1
That wipes out the whole release history and breaks rollbacks. Instead, edit the secret:
# 1. Export the secret to a temporary file
kubectl get secret sh.helm.release.v1.myapp.v1 -o jsonpath="{.data.release}" | base64 -d > /tmp/release.yaml
# 2. Remove the CRD section (look for "kind: CustomResourceDefinition")
sed -n '/kind: CustomResourceDefinition/,$p' /tmp/release.yaml > /tmp/clean.yaml
# 3. Re‑encode and patch the secret back
cat /tmp/clean.yaml | base64 | kubectl patch secret sh.helm.release.v1.myapp.v1 -p '{"data":{"release":"$(cat -)"}}' --type=merge
Now helm upgrade myapp ./myapp runs without the error. The downside is you lose the ability to roll back past the point where the CRD was removed.
Pro Tip: Always back up the original secret (
kubectl get secret … -o yaml > backup.yaml) before you start editing. Restoring is as easy askubectl apply -f backup.yaml.
2️⃣ Use the --skip-crds flag on upgrade and manage CRDs separately
Helm 3.14 introduced --skip-crds, which tells Helm to ignore the crds/ directory entirely during an upgrade. This is the cleanest approach if you have a separate process (e.g., a GitOps operator) that applies CRDs.
Problematic usage:
helm upgrade myapp ./myapp # fails because CRDs are still processed
Fixed usage:
helm upgrade myapp ./myapp --skip-crds
With --skip-crds Helm only touches the non‑CRD resources, so the name‑reuse check is bypassed. The trade‑off is you must guarantee that the CRDs are already present and at the correct version – otherwise you might end up with a mismatched schema.
3️⃣ Patch the existing CRD to remove the helm.sh/resource‑policy: keep annotation
If the CRD is owned by the cluster (as is the case with AKS built‑in CRDs), the keep annotation tells Helm “don’t delete this on uninstall”. Unfortunately it also triggers the name‑reuse guard on upgrade. Removing the annotation restores Helm’s classic “install‑once‑don’t‑touch‑again” semantics.
kubectl annotate crd foos.example.com helm.sh/resource-policy- # note the trailing dash removes the annotation
After the annotation is gone, a normal helm upgrade works. This method is safe as long as you control the lifecycle of the CRD and no other tool depends on the annotation.
How do I prevent the issue from resurfacing after a cluster upgrade?
- Pin Helm version in CI – lock to 3.13.x until you have a documented migration path.
- Separate CRD lifecycle – store CRDs in a dedicated repo and apply them with
kubectl apply -for a GitOps controller before any Helm release runs. - Add a post‑upgrade hook that removes the
keepannotation if you must keep the CRD in the chart. - Monitor Helm release secrets – a simple
kubectl get secret -l owner=helmscript can alert you when a secret contains a CRD manifest that you didn’t expect.
FAQ
Why does Helm treat a CRD that already exists as a name‑reuse error?
Helm 3.14 added a version‑check for CRDs. If the stored manifest in the release secret differs from the one on the cluster, Helm aborts with the generic “cannot re‑use a name” error to avoid silent schema changes.
Can I downgrade Helm to avoid the problem?
Yes, downgrading to Helm 3.13 or earlier removes the stricter CRD check, but you lose other bug fixes and security patches. It’s a temporary workaround, not a long‑term solution.
Is the --skip-crds flag safe for production?
It is safe if you have a separate, reliable process that guarantees the CRDs are present and version‑matched. Otherwise you risk deploying resources that depend on a missing or outdated CRD.
How do I know which CRDs are causing the collision?
Run helm get manifest <release> and grep for kind: CustomResourceDefinition. Compare the output with kubectl get crd -o yaml to spot mismatches.
Will this issue appear on other cloud providers (EKS, GKE)?
Only if the provider ships built‑in CRDs with the helm.sh/resource-policy: keep annotation. AKS does it for several Azure‑specific CRDs; EKS and GKE currently do not, so the problem is less common there.
Conclusion
The “cannot re‑use a name that is still in use” error on AKS 1.29 isn’t a mysterious Helm bug – it’s the result of Helm 3.14’s stricter CRD validation colliding with Azure’s built‑in CRDs. By understanding the three root causes (CRD version mismatch, keep annotation, and Helm secret history) you can pick the right remediation: edit the release secret, use --skip-crds, or clean up the annotation. Whichever path you take, make the fix part of your CI pipeline so future AKS upgrades don’t silently re‑introduce the same collision.
Pro Tip: Add a pre‑upgrade step in your pipeline that runs
kubectl get crd -L helm.sh/resource-policyand fails fast if any CRD still carries thekeepannotation.
Keeping Helm happy on AKS 1.29 is mostly about explicitly managing CRDs rather than letting Helm guess. Once you separate the CRD lifecycle, upgrades become predictable again, and you can safely enjoy the latest Helm and Kubernetes features.
Stay tuned for more deep dives into real‑world DevOps pain points.
Follow SpiritCode for more hands‑on engineering stories like this.

