# Fix Helm "Another Operation Is in Progress": 3 Safe Fixes and the CI Settings That Prevent It

URL: https://www.devopsness.com/blog/fix-helm-another-operation-in-progress
Author: Kiril Urbonas
Published: 2026-10-31
Updated: 2026-10-31
Categories: DevOps
Tags: Helm, Kubernetes, Troubleshooting, CI/CD, DevOps

Summary: 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.

---
# Fix Helm "Another Operation Is in Progress": 3 Safe Fixes and the CI Settings That Prevent It

`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]
> 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](/blog/fix-terraform-state-lock): prove nobody is actually running, then clear the stale marker.

## Diagnose in three commands

```bash
$ 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`):

```bash
$ 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:

```bash
$ 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:

```bash
$ 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:

```bash
$ 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.

```bash
# 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:**

```yaml
# .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](/blog/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](/blog/helm-vs-kustomize) is the companion read. The caveat: rollback reruns hooks, so check what yours do first.

## Sources

- [Helm docs: helm upgrade (Helm 4)](https://helm.sh/docs/helm/helm_upgrade/)
- [Helm docs: helm rollback](https://helm.sh/docs/helm/helm_rollback/)
- [Helm docs: Helm 4 overview, renamed CLI flags](https://helm.sh/docs/overview/)
- [Helm source: pkg/action/upgrade.go, the pending-release check](https://github.com/helm/helm/blob/v4.3.1/pkg/action/upgrade.go)

