Helm ignores ownership during upgrade
What happened?
When a resource is transferred from one release to another using --take-ownership, and then the original release is upgraded to a version that no longer includes that resource, helm deletes the resource from the cluster — even though it is now owned by a different release.
This was recently fixed for the uninstall case in PR #31584 (which resolved #31333), where verifyOwnershipBeforeDelete now correctly skips resources not owned by the release being uninstalled. However, the upgrade path has no equivalent ownership check.
When helm computes the diff between old and new manifests during upgrade, it deletes orphaned resources based solely on whether helm.sh/resource-policy: keep is present — it does not check meta.helm.sh/release-name or meta.helm.sh/release-namespace.
The deletion happens in pkg/kube/client.go in the update() function (around line 647):
for _, info := range originals.Difference(targets) {
// ...
if annotations != nil && annotations[ResourcePolicyAnno] == KeepPolicy {
continue
}
if err := deleteResource(info, metav1.DeletePropagationBackground); err != nil {This checks helm.sh/resource-policy: keep but does not check meta.helm.sh/release-name. Adding an ownership check here (similar to verifyOwnershipBeforeDelete in the uninstall path) would complete the feature.
What did you expect to happen?
When upgrading release-a to a version that no longer includes a resource, helm should check the live ownership annotations before deleting. If meta.helm.sh/release-name on the resource points to a different release (e.g. release-b), it should skip deletion — the same way helm uninstall now does after PR #31584.
How can we reproduce it (as minimally and precisely as possible)?
# Create two simple charts:
# chart-a-v1: contains ConfigMap "shared-config" + ConfigMap "chart-a-own"
# chart-a-v2: contains only ConfigMap "chart-a-own" (shared-config removed)
# chart-b-v1: contains ConfigMap "shared-config" + ConfigMap "chart-b-own"
# Step 1: Install chart-a v1
helm install release-a ./chart-a-v1 -n test
# Step 2: Install chart-b, adopting shared-config
helm install release-b ./chart-b-v1 -n test --take-ownership
# Verify ownership transferred:
kubectl get configmap shared-config -n test -o jsonpath='{.metadata.annotations}'
# {"meta.helm.sh/release-name":"release-b","meta.helm.sh/release-namespace":"test"}
# Step 3: Upgrade release-a to v2 (removes shared-config from its manifest)
helm upgrade release-a ./chart-a-v2 -n test
# Result: shared-config is DELETED, even though release-b owns it
kubectl get configmap shared-config -n test
# Error from server (NotFound): configmaps "shared-config" not foundFor contrast, uninstall works correctly:
# (same setup as above, steps 1-2)
helm uninstall release-a -n test
# level=WARN msg="skipping delete of resource not owned by this release" kind=ConfigMap name=shared-config
# 1 resource(s) were not deleted because they are not owned by this release:
# [ConfigMap] shared-config
# shared-config survives
kubectl get configmap shared-config -n test
# NAME DATA AGE
# shared-config 2 30s
``
### Helm version
<details>
```console
$ helm version
version.BuildInfo{Version:"v4.2.0", GitCommit:"06468084e85c244c712834933d25ea232a4c2093", GitTreeState:"clean", GoVersion:"go1.26.3", KubeClientVersion:"v1.36"}Kubernetes version
$ kubectl version
Client Version: v1.35.2
Server Version: v1.35.0Source: helm/helm