How to Fix Helm UPGRADE FAILED Errors: Causes and Commands (2026)

Oct 31, 2026 · 13 min · Ajay Kumar

This guide is for anyone whose helm upgrade just printed Error: UPGRADE FAILED and who needs the release deploying again without making things worse. By the end you will be able to read a release's real state from helm history and its storage Secrets, and fix the five failures behind most of these errors: a release stuck in a pending state, "has no deployed releases", objects Helm refuses to adopt, wait timeouts, and the server-side apply conflicts that are new in Helm 4. Error strings and flags are checked against the Helm v4.3.1 and v3.22.1 source, both released on 15 October 2026; where Helm 3 and 4 differ, both are shown, and elsewhere Helm 3 users should read --wait=watcher as --wait and --rollback-on-failure as --atomic. You need kubectl access to the release namespace, including permission to read and delete Secrets, and Helm 3.17 or later for --take-ownership.

helm upgrade or install fails Step 1: helm history RELEASE -n NAMESPACE --max 5 read STATUS and DESCRIPTION of the newest revision pending-upgrade another operation is in progress: rollback to last deployed revision no deployed releases wrong -n or context, or no --install: helm list -A, then upgrade --install already exists invalid ownership metadata: add label and annotations, or --take-ownership not ready, timeout deadline exceeded: describe, events, logs; fix the pod, then upgrade again SSA conflict Apply failed with N conflicts: drop the field or use --force-conflicts Then: helm upgrade again, confirm with helm status
Decision flow for the five Helm upgrade failures covered below. Error strings come from the Helm v4.3.1 and v3.22.1 source code.

Step 1: Read the release state before changing anything

Every one of these errors is Helm comparing what you asked for with what it has recorded, so start with the record. helm history shows the newest revisions, their status and the description Helm wrote when each finished or failed:

# Works in Helm 3.22 and Helm 4.3
helm history api -n prod --max 5
REVISION  UPDATED                   STATUS           CHART      APP VERSION  DESCRIPTION
5         Mon Oct 19 09:12:44 2026  superseded       api-1.8.0  1.8.0        Upgrade complete
6         Tue Oct 20 14:03:10 2026  deployed         api-1.8.1  1.8.1        Upgrade complete
7         Fri Oct 23 16:41:27 2026  pending-upgrade  api-1.9.0  1.9.0        Preparing upgrade

A revision moves through a small set of states defined in the release status code: pending-install, pending-upgrade or pending-rollback while Helm works, then deployed or failed, with older successful revisions marked superseded, plus uninstalling, uninstalled and unknown. A pending-* status on the newest revision that is older than your --timeout is almost always a dead Helm process.

With the default storage driver each revision is one Secret named sh.helm.release.v1.<release>.v<revision>, labelled with the release name, status and version. The driver can be switched to configmap, memory or sql with HELM_DRIVER, but nearly everyone runs the default:

kubectl get secrets -n prod -l owner=helm,name=api \
  -o custom-columns=NAME:.metadata.name,STATUS:.metadata.labels.status,VERSION:.metadata.labels.version
NAME                        STATUS            VERSION
sh.helm.release.v1.api.v5   superseded        5
sh.helm.release.v1.api.v6   deployed          6
sh.helm.release.v1.api.v7   pending-upgrade   7

The labels are an index; the truth is the encoded release inside. Helm stores gzipped JSON, base64-encoded, and Kubernetes base64-encodes Secret data again, so decoding takes two passes:

kubectl get secret sh.helm.release.v1.api.v7 -n prod -o jsonpath='{.data.release}' \
  | base64 -d | base64 -d | gzip -dc \
  | jq '{name, version, status: .info.status, description: .info.description}'
{
  "name": "api",
  "version": 7,
  "status": "pending-upgrade",
  "description": "Preparing upgrade"
}

Note one Helm 4 change before you script any of this: helm list now shows every status by default and the -a flag is gone, so helm list -a fails with unknown shorthand flag: 'a' in -a. helm list --pending -A works in both versions.

Fix 1: UPGRADE FAILED: another operation (install/upgrade/rollback) is in progress

Error: UPGRADE FAILED: another operation (install/upgrade/rollback) is in progress

Helm refuses to upgrade when the newest revision is pending-install, pending-upgrade or pending-rollback. The upgrade code describes this check as a pessimistic lock against concurrent upgrades. That lock is only released when the Helm process finishes. Helm handles SIGINT and SIGTERM by marking the release failed, so a revision left pending usually means Helm died or lost the cluster before it could record the result: a CI runner that killed the process tree, an OOM-killed runner pod, a dropped VPN, a closed laptop lid. On GitHub Actions, cancellation sends SIGINT to the step's shell, SIGTERM after 7.5 seconds and kills the whole process tree 2.5 seconds later, which is not always enough for Helm to record the failure.

First, make sure the operation really is dead. Check for another pipeline deploying the same release, and for hook Jobs still running:

# Most charts label hook Jobs with the instance label too; drop -l if yours does not
kubectl get jobs,pods -n prod -l app.kubernetes.io/instance=api

If a deployed revision exists, roll back to it. helm rollback does not check for pending state: it applies revision 6 over whatever revision 7 left behind and writes revision 8. Revision 7 stays in history as pending-upgrade, which is harmless because Helm only checks the newest revision.

# Helm 4.3: named wait strategy; --description is new in 4.3.0
helm rollback api 6 -n prod --wait=watcher --timeout 10m \
  --description "recover from stuck pending-upgrade (revision 7)"

# Helm 3.22: same command, boolean wait
helm rollback api 6 -n prod --wait --timeout 10m

If the stuck revision is pending-install on revision 1, there is nothing to roll back to. Uninstall (it works on pending releases) and install again:

helm uninstall api -n prod --wait
helm upgrade --install api ./chart -n prod --wait=watcher --timeout 10m

The last resort is deleting the pending revision's Secret, which makes revision 6 the newest again. Back it up first. Objects the half-finished upgrade created already carry this release's ownership metadata, so the next upgrade adopts them rather than failing:

kubectl get secret sh.helm.release.v1.api.v7 -n prod -o yaml > api-v7-pending.yaml
kubectl delete secret sh.helm.release.v1.api.v7 -n prod
helm history api -n prod --max 3   # revision 6 should now be the newest, status deployed

Then stop it recurring. Serialise deploys per environment and give the CI job more time than Helm needs, so Helm fails cleanly instead of being killed:

# .github/workflows/deploy.yml (fragment)
concurrency:
  group: deploy-prod
  cancel-in-progress: false   # queue the next deploy; never kill a running helm upgrade
jobs:
  deploy:
    runs-on: ubuntu-24.04
    timeout-minutes: 20       # longer than helm --timeout 10m plus hooks
    steps:
      - uses: actions/checkout@v7
      - run: ./deploy.sh      # helm upgrade --install ... --rollback-on-failure --timeout 10m

Fix 2: UPGRADE FAILED: "api" has no deployed releases

Error: UPGRADE FAILED: "api" has no deployed releases

In current Helm this almost always means Helm found no release by that name where it looked. The upgrade code deliberately returns this message when the release does not exist at all, so the usual causes are a missing -n, the wrong kube-context, or running helm upgrade without --install for a first deploy. The other cause is a history whose newest revision is uninstalled (from helm uninstall --keep-history), uninstalling or unknown, with no deployed revision to upgrade from.

Older Helm 3 releases also returned this after a failed first install, which is why old answers say to delete the release. Helm 3.22 and 4.3 upgrade from a failed or superseded newest revision instead.

# Where is it, and in what state? Helm 4 lists all statuses by default
helm list -A --filter '^api$'
# Helm 3 needs -a to include failed, pending and uninstalled releases
helm list -A -a --filter '^api$'

kubectl config current-context

# Installs if missing, and replaces a release left as 'uninstalled' by --keep-history
helm upgrade --install api ./chart -n prod --wait=watcher --timeout 10m

Fix 3: Resource exists and cannot be imported into the current release

Error: INSTALLATION FAILED: unable to continue with install: ServiceAccount "api" in namespace "prod"
exists and cannot be imported into the current release: invalid ownership metadata;
label validation error: missing key "app.kubernetes.io/managed-by": must be set to "Helm";
annotation validation error: missing key "meta.helm.sh/release-name": must be set to "api";
annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "prod"

That is the Helm 4.3.1 wording, printed on one line; an upgrade says UPGRADE FAILED: unable to continue with update. Helm 3.13 to 3.22 capitalise it as Unable to continue with install, and Helm 3.12 and earlier prefixed it with rendered manifests contain a resource that already exists, which is the phrase most search results still use.

Helm only adopts an existing object if it already carries the label app.kubernetes.io/managed-by: Helm and the annotations meta.helm.sh/release-name and meta.helm.sh/release-namespace matching this release, as validate.go shows. The object usually came from kubectl apply, another tool, or a previous release that left it behind. Pick one fix:

# A. Adopt it deliberately (cluster-scoped objects still need the namespace annotation)
kubectl -n prod label serviceaccount api app.kubernetes.io/managed-by=Helm --overwrite
kubectl -n prod annotate serviceaccount api \
  meta.helm.sh/release-name=api meta.helm.sh/release-namespace=prod --overwrite

# B. Let Helm take it without the check (Helm 3.17+ and Helm 4)
helm upgrade --install api ./chart -n prod --take-ownership

# C. Delete it if it is disposable (never a PVC or a Secret holding data)
kubectl -n prod delete serviceaccount api

If the message instead says key "meta.helm.sh/release-name" must equal "api": current value is "api-legacy", two releases render the same object. Rename it in one chart or turn it off there. Taking ownership just means the other release's next upgrade fails the same way. Helm 4.3.0 also added an ownership check before uninstall deletes resources, so uninstalling one release no longer removes an object that belongs to another.

Fix 4: Timed out waiting for the condition, or context deadline exceeded

The wording depends on version and on what was being waited for:

# Helm 4, --wait=watcher (also the default with --rollback-on-failure)
Error: UPGRADE FAILED: resource Deployment/prod/api not ready. status: InProgress, message: Available: 0/3
context deadline exceeded

# Helm 3.22 with --wait
Error: UPGRADE FAILED: context deadline exceeded

# Helm 3 hook Job that never completed
Error: UPGRADE FAILED: pre-upgrade hooks failed: timed out waiting for the condition

Helm is only the messenger here. It waited up to --timeout (default 5m, applied to each wait and each hook) and the objects never became ready. Find out why from the objects themselves:

kubectl get pods -n prod -l app.kubernetes.io/instance=api
kubectl describe deploy api -n prod | sed -n '/Conditions:/,$p'
kubectl get events -n prod --sort-by=.lastTimestamp | tail -20
kubectl logs -n prod job/api-db-migrate --all-containers   # for a hook Job

The usual answers are a crash loop (see how to fix CrashLoopBackOff), an image that cannot be pulled (ImagePullBackOff), Pods that never schedule (stuck in Pending), a failing readiness probe, or a migration hook that is simply slow. Raise --timeout only for the last one. After the fix the release is failed, and a plain helm upgrade proceeds from there.

One Helm 4 trap: the kstatus watcher also waits on custom resources. If an operator never sets a Ready condition on its objects, --wait=watcher times out on a release Helm 3 called healthy. --wait=legacy uses the Helm 3 checks while you get the operator fixed.

Fix 5: Apply failed with 1 conflict (Helm 4 server-side apply)

New installs in Helm 4 use server-side apply, and so does any release you upgrade with --server-side=true. The API server then rejects a change to a field another manager owns:

Error: UPGRADE FAILED: conflict occurred while applying object prod/api apps/v1, Kind=Deployment:
Apply failed with 1 conflict: conflict with "kubectl-edit" using apps/v1: .spec.template.spec.containers[name="api"].image

The prefix comes from Helm's kube client and the rest from the Kubernetes API server. List who manages the object:

kubectl get deploy api -n prod --show-managed-fields \
  -o jsonpath='{range .metadata.managedFields[*]}{.manager}{"\t"}{.operation}{"\t"}{.subresource}{"\n"}{end}'
helm                      Apply
kubectl-edit              Update
kube-controller-manager   Update   status

Then choose, following the server-side apply docs. If the chart should win, rerun with --force-conflicts, which makes Helm the owner (it cannot be combined with --force-replace). If the other writer should own the field, remove it from the chart; the classic case is replicas under a HorizontalPodAutoscaler. To defer the decision, --server-side=false keeps the release on client-side apply. Do not expect a dry run to warn you: in 4.3.1 helm upgrade --dry-run stops before the apply, so conflicts only appear on a real upgrade.

Roll back safely with helm rollback and helm history

helm rollback RELEASE [REVISION] re-applies a previous revision's manifest and values as a new revision; with no revision, or 0, it goes back one. It does not roll back data, so a schema migration your hook ran stays applied, and Helm never upgrades or deletes CRDs from a chart's crds/ directory. --history-max defaults to 10 for upgrades and rollbacks, so old revisions get pruned, and a missing target returns release has no 3 version.

helm history api -n prod --max 10
helm get values api -n prod --revision 6          # values the good revision used
helm rollback api 6 -n prod --wait=watcher --timeout 10m
helm history api -n prod --max 3 --show-rollback-revision   # ROLLBACK column, Helm 4.3+
REVISION  UPDATED                   STATUS      CHART      APP VERSION  ROLLBACK  DESCRIPTION
6         Tue Oct 20 14:03:10 2026  superseded  api-1.8.1  1.8.1                  Upgrade complete
7         Fri Oct 23 16:41:27 2026  failed      api-1.9.0  1.9.0                  Upgrade "api" failed: context deadline exceeded
8         Sat Oct 24 08:02:51 2026  deployed    api-1.8.1  1.8.1        6         Rollback to 6

To make this automatic, --rollback-on-failure (Helm 4's name for --atomic) rolls back to the last successful revision when an upgrade fails and switches the wait strategy to watcher.

Troubleshooting

Symptom Likely cause Fix
another operation (install/upgrade/rollback) is in progress Newest revision stuck in pending-* after Helm was killed, or a concurrent deploy Confirm nothing is running, then helm rollback to the last deployed revision; delete the pending Secret as a last resort
"api" has no deployed releases Wrong namespace or context, or helm upgrade without --install helm list -A, check the context, use helm upgrade --install
exists and cannot be imported into the current release Object lacks Helm's ownership label and annotations Add them with kubectl label/annotate, or use --take-ownership
key "meta.helm.sh/release-name" must equal ... Two releases render the same object Rename or disable it in one chart
not ready. status: InProgress or context deadline exceeded Pods never became ready kubectl describe, events and logs; fix the Pod, then upgrade
pre-upgrade hooks failed Hook Job failed or ran past --timeout kubectl logs job/<hook>; fix it or raise --timeout
Apply failed with 1 conflict Another field manager owns the field under server-side apply Remove the field from the chart, --force-conflicts, or --server-side=false
unknown shorthand flag: 'a' in -a Helm 4 removed helm list -a Drop -a; Helm 4 lists every status by default
cannot reuse a name that is still in use helm install on a name that already exists (Helm 3 spells it re-use) Use helm upgrade --install
release has no 3 version Rollback target pruned by --history-max Pick a revision that helm history still lists

FAQ

Is it safe to delete a Helm release Secret?

Deleting the newest revision's Secret when it is stuck in pending-* is a common, recoverable fix, because the previous revision becomes the newest again. Back it up with kubectl get secret ... -o yaml first. Never delete the only deployed revision unless you intend to reinstall, because Helm will then treat the release as missing.

How do I fix "another operation is in progress" without losing the release?

Run helm rollback RELEASE N to the last revision whose status is deployed. Rollback works while the newest revision is pending, keeps the full history, and writes a new revision. Make sure no other Helm process is still running against the release first.

Does helm rollback create a new revision?

Yes. Rolling back from revision 7 to 6 writes revision 8 with the description Rollback to 6 and marks the previously deployed revision superseded. Helm 4.3 adds --description to record why, and helm history --show-rollback-revision to show which revision each rollback restored.

Why does Helm 4 time out where Helm 3 did not?

With --wait or --rollback-on-failure, Helm 4 uses a kstatus watcher that also checks custom resources and generation-based rollout status. Objects that Helm 3's fixed list of checks ignored can now hold up a release. Fix the object's readiness, or use --wait=legacy temporarily.

In production the first thing I check is helm history, not the error, because the newest revision's status and description tell me which of these five problems I have before I read a log. The second is whether another pipeline is touching the same release, because a stuck pending-upgrade almost always means two deploys raced or a runner was killed mid-flight. If you are planning the Helm 4 move itself, the sibling post on what changed in Helm 4 and when Helm 3 ends covers the flag renames and server-side apply defaults. Official references: helm upgrade, helm rollback, helm history and the Helm 4 overview.

Related: How to Set Up Argo CD for GitOps: Step-by-Step Guide (2026), How to Fix Kubernetes CrashLoopBackOff: Causes and Commands (2026) and How to Do Canary Deployments with Argo Rollouts 1.10 (2026).

I'm Ajay Kumar — I build and operate PandaStack, an open-source Firecracker microVM cloud for AI agents, and write about the infrastructure and tooling around it.

Need this kind of infrastructure work? See what I do or email hello@ajayk.sh.


Related