Fix Helm "Another Operation Is in Progress": 3 Safe Fixes and the CI Settings That Prevent It
An interrupted upgrade left your release stuck in pending-upgrade, and Helm now refuses to touch it. Confirm nothing is running, then roll back or remove the one stuck revision.
Key takeaways
- Helm's "another operation (install/upgrade/rollback) is in progress" error means the release's latest revision is stuck in pending-install, pending-upgrade or pending-rollback, usually because a deploy was killed mid-run.
- Confirm nothing is running, then helm rollback to the last deployed revision, or delete only the stuck release secret.
- Prevent it with CI concurrency and timeouts.
On this page
Error: UPGRADE FAILED: another operation (install/upgrade/rollback) is in progress means the newest revision of your release is stuck in a pending-* status, almost always because a deploy was killed before Helm could record the result. Confirm nothing is running, then helm rollback to the last good revision. Your workloads are usually fine; Helm's bookkeeping is not.
Verified against
Helm 4.3.1 (released 2026-10-15) and Helm 3 docs, against the helm.sh upgrade and rollback command references, the Helm 4 overview, and the error, status and storage code in the v4.3.1 source. Checked October 2026.
Why Helm refuses
Helm keeps every revision of a release as a Kubernetes Secret, and each revision carries a status. Three of them mean "something is happening right now": pending-install, pending-upgrade and pending-rollback. Before an upgrade, Helm loads the latest revision and, if its status is any of those three, returns this error without touching the cluster. A comment in the Helm source calls this check a pessimistic lock against concurrent upgrades.
The problem is what happens when the process holding that "lock" dies. Helm writes the pending-upgrade revision first, applies the manifests, waits, then flips the status to deployed or failed. Kill it in between (a cancelled CI job, a runner that hit its timeout, a laptop that slept, Ctrl-C) and the revision stays pending-upgrade forever. Nothing expires it.
So the fix is the same shape as a stuck Terraform state lock: prove nobody is actually running, then clear the stale marker.
Diagnose in three commands
$ helm status myapp -n prod
NAME: myapp
NAMESPACE: prod
STATUS: pending-upgrade
REVISION: 7
$ helm history myapp -n prod
REVISION UPDATED STATUS CHART DESCRIPTION
5 Tue Oct 28 09:12:40 2026 superseded myapp-1.8.0 Upgrade complete
6 Wed Oct 29 14:03:11 2026 deployed myapp-1.8.1 Upgrade complete
7 Thu Oct 30 16:41:55 2026 pending-upgrade myapp-1.9.0 Preparing upgrade
$ kubectl get secrets -n prod -l owner=helm,name=myapp
NAME TYPE DATA AGE
sh.helm.release.v1.myapp.v5 helm.sh/release.v1 1 2d
sh.helm.release.v1.myapp.v6 helm.sh/release.v1 1 1d
sh.helm.release.v1.myapp.v7 helm.sh/release.v1 1 19h
What to read: the stuck revision (7), the last one with deployed (6), and the age of the pending one. A pending-upgrade that is 19 hours old is stale. One that is 40 seconds old may be a colleague's deploy still running; wait for it.
To find every stuck release in the cluster at once, query the labels Helm puts on each release secret (owner, name, status, version):
$ kubectl get secrets -A -l 'owner=helm,status in (pending-install,pending-upgrade,pending-rollback)'
Fix 1: roll back to the last deployed revision (preferred)
Before anything else, check CI for a running deploy job on this release and ask in the team channel. Then:
$ helm rollback myapp 6 -n prod --wait --timeout 10m
Rollback was a success! Happy Helming!
Rollback doesn't run the pending check, so it works on a stuck release. It creates a new revision (8) from revision 6's manifests and marks it deployed, which unblocks the next upgrade. Pass the revision number explicitly: with no number Helm targets the revision just before the current one, which is right here but wrong if there are several stuck or failed revisions in a row.
Use this when the half-applied upgrade may have left the cluster in a mixed state. Rolling back reapplies a known-good set of manifests.
Fix 2: delete only the stuck release secret
If the cluster already matches what you want, or rollback hooks would cause harm, remove the one pending revision record:
$ kubectl get secret sh.helm.release.v1.myapp.v7 -n prod -o yaml > myapp.v7.backup.yaml
$ kubectl delete secret sh.helm.release.v1.myapp.v7 -n prod
$ helm history myapp -n prod # revision 6 is now the latest
$ helm upgrade myapp ./chart -n prod -f values-prod.yaml --wait --timeout 10m
Back it up first, delete exactly one secret, and only ever the highest revision. Deleting the deployed revision instead loses the release's record of what it owns. Resources the interrupted upgrade created stay in the cluster; the next upgrade reconciles them.
Don't try to patch the status label to failed instead. Helm reads the status from the encoded release stored inside the secret; the label only exists so queries like the one above work.
Fix 3: a first install stuck in pending-install
If the release has only one revision and it is pending-install, there is nothing to roll back to. helm rollback myapp fails with release has no 0 version. Uninstall and install again:
$ helm uninstall myapp -n prod
$ helm upgrade --install myapp ./chart -n prod -f values-prod.yaml --wait --timeout 10m
Prevention: the CI settings that stop this recurring
Fail cleanly instead of dying mid-flight. Make Helm's own timeout shorter than the CI job's, so Helm gets to record failed before the runner kills it. In Helm 4 the flags were renamed: --atomic is now --rollback-on-failure and --force is --force-replace. The old names still work but print a deprecation warning.
# Helm 4
$ helm upgrade --install myapp ./chart -n prod -f values-prod.yaml \
--rollback-on-failure --timeout 10m --history-max 10
# Helm 3
$ helm upgrade --install myapp ./chart -n prod -f values-prod.yaml \
--atomic --timeout 10m --history-max 10
With --rollback-on-failure, Helm 4 defaults --wait to the watcher strategy; without any --wait, the default is hookOnly, which only waits for hooks. --timeout defaults to 5m0s per Kubernetes operation, and --history-max keeps 10 revisions by default.
Serialize deploys per release and never cancel a running one:
# .github/workflows/deploy.yml
concurrency:
group: helm-prod-myapp
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 20 # longer than helm --timeout
cancel-in-progress: true is the setting to hunt for: a new push cancels the job that was halfway through an upgrade, and the release is left pending-upgrade. If chart structure is part of the problem, our list of Helm chart anti-patterns covers hooks and waits that make upgrades slow enough to get killed.
Where this breaks
Everything here assumes the default Secrets storage driver. With HELM_DRIVER=configmap the records are ConfigMaps with the same naming, and with the SQL driver they are rows. Argo CD renders charts with helm template and never creates release secrets, so this error doesn't apply there; a controller that does run real Helm releases should be left to remediate its own, or it will fight your manual fix. And --rollback-on-failure rolls back to the previous release, which can itself be broken if the last good deploy is several revisions back.
Questions people ask
How do I fix "another operation (install/upgrade/rollback) is in progress"?
Run helm history <release> -n <ns> to find the stuck pending-* revision and the last deployed one. Confirm no deploy is actually running, then helm rollback <release> <last-deployed-revision>. If rollback isn't appropriate, back up and delete only the stuck secret, sh.helm.release.v1.<release>.v<N>, and upgrade again. For a stuck first install, uninstall and reinstall.
Is it safe to delete Helm release secrets?
Deleting the single newest secret for a stuck pending-* revision is safe once nothing is running: Helm falls back to the previous revision, and your workloads are untouched. Deleting older or deployed revisions is not, because those records are how Helm knows which resources the release owns. Always back up the secret with kubectl get secret -o yaml before deleting it.
What is the difference between --atomic and --rollback-on-failure?
They are the same behavior under two names. Helm 4 renamed --atomic to --rollback-on-failure to make clearer what it does: if the upgrade fails, Helm rolls back to the previous successful release. In Helm 4 --atomic still works but prints a deprecation warning. Both turn on waiting, so set --timeout deliberately.
The call we'd make
Treat the error as a stale lock, not an emergency. Check CI and the team, helm rollback to the last deployed revision, and keep secret deletion for when a rollback would do harm. Then add a concurrency group with cancel-in-progress: false, a Helm timeout shorter than the job timeout, and --rollback-on-failure. If you are deciding how much Helm to use at all, Helm vs Kustomize is the companion read. The caveat: rollback reruns hooks, so check what yours do first.
Sources
Cite this article
Kiril Urbonas (2026). Fix Helm "Another Operation Is in Progress": 3 Safe Fixes and the CI Settings That Prevent It. DevOpsNess. https://www.devopsness.com/blog/fix-helm-another-operation-in-progress
[Fix Helm "Another Operation Is in Progress": 3 Safe Fixes and the CI Settings That Prevent It](https://www.devopsness.com/blog/fix-helm-another-operation-in-progress) (Kiril Urbonas, DevOpsNess, 2026-10-31)
More from DevOps
Explore more articles in this category
15 SRE Interview Questions With Answers, From SLOs to On-Call
SRE interviews test numbers and judgment. Fifteen questions on SLIs, error budgets, incident command, toil, capacity, on-call and postmortems, plus a systems-design scenario, each with the answer that scores and the one that loses the room.
Sign Container Images With Cosign: Keyless GitHub Actions Workflow, SBOM and In-Cluster Checks
One workflow that builds, signs keylessly with GitHub OIDC, attaches an SBOM and SLSA provenance, plus the Kyverno policy that refuses anything unsigned. Copy it, change three names, ship it.
Fix Prometheus High Cardinality: 6 Commands to Find and Drop Bad Series
When Prometheus runs out of memory, one label is almost always to blame. Six commands to find it, the relabel rules that drop it, and the scrape limits that stop it coming back.
You might have missed
Evergreen posts worth revisiting.