Helm marks a release pending-install, pending-upgrade or pending-rollback while an operation runs, and refuses any concurrent operation. If the process doing the work dies, nothing clears the marker and every subsequent command is refused.
The usual causes are a cancelled CI job, a runner terminated mid-deploy, a laptop closed during an upgrade, or helm upgrade --wait timing out in a way that left the state behind.
Is it actually running?
Do not skip this. Clearing a live operation causes real damage.
helm list -n production --all --filter '^api$'
NAME REVISION UPDATED STATUS CHART
api 4 2026-09-21 06:12:09 UTC pending-upgrade api-1.4.2
Compare UPDATED against now. A pending state from four hours ago, with a default Helm timeout of five minutes, is abandoned.
Then look at whether anything is moving:
kubectl get pods -n production -l app.kubernetes.io/instance=api
kubectl rollout status deployment/api -n production --timeout=10s
Pods actively being created or terminated means the operation may still be progressing, and waiting is correct. A stable set of Pods with an old timestamp means nothing is happening.
Clear it
From Helm 3.13 the supported route is a rollback:
helm rollback api -n production
That creates a new revision from the last successful one and clears the pending marker. Check it worked:
helm list -n production --all --filter '^api$'
helm history api -n production
When rollback is refused
A release stuck in pending-install has no successful revision to roll back to:
helm history api -n production
REVISION STATUS CHART
1 pending-install api-1.4.2
Uninstall it, since nothing was ever deployed:
helm uninstall api -n production
kubectl get all -n production -l app.kubernetes.io/instance=api # check for leftovers
helm install api ./chart -n production
The last resort: delete the revision Secret
Only when rollback and uninstall both fail, and only after confirming nothing is running.
Helm stores each revision as a Secret. Deleting the pending one makes the previous revision current again:
kubectl get secret -n production -l owner=helm,name=api \
--sort-by=.metadata.creationTimestamp \
-o custom-columns=NAME:.metadata.name,STATUS:.metadata.labels.status
NAME STATUS
sh.helm.release.v1.api.v3 superseded
sh.helm.release.v1.api.v4 pending-upgrade
kubectl delete secret sh.helm.release.v1.api.v4 -n production
Helm now sees revision 3 as current. Note that this only changes Helm's bookkeeping: whatever the interrupted upgrade already applied to the cluster stays applied. So Helm's view and reality can differ, and the next helm upgrade reconciles them. Run a diff first if you can:
helm diff upgrade api ./chart -n production # needs the helm-diff plugin
Preventing it
Use --atomic with a realistic --timeout:
helm upgrade --install api ./chart -n production \
--atomic --timeout 10m
--atomic rolls back automatically if the upgrade fails or times out, so a failure leaves a clean deployed release rather than a stuck pending one. It implies --wait.
The timeout has to exceed your slowest realistic rollout. The default is five minutes, which a large StatefulSet or a slow image pull can exceed easily, and then --atomic rolls back a deployment that was actually fine.
Make the CI job hard to interrupt mid-operation. Where the platform allows it, mark the deploy step non-cancellable, or set the job timeout above the Helm timeout so the job cannot be killed while Helm is working.
Do not run two deploys at once. Concurrency controls on the pipeline prevent two jobs upgrading the same release:
# GitHub Actions
concurrency:
group: deploy-production
cancel-in-progress: false
cancel-in-progress: false matters. Cancelling an in-flight deploy is precisely how releases get stuck.
Helm 3 has no lock
Worth knowing: there is no distributed lock. The pending status in the release Secret is the only guard, and it is advisory. Two helm upgrade commands started at the same instant can both proceed, which produces interleaved changes and a release history that does not describe reality. Pipeline-level concurrency control is the actual protection.
A checklist
helm list -n <ns> --alland readUPDATED. Compare against your timeout.kubectl get pods -l app.kubernetes.io/instance=<release>. Anything moving?- Genuinely running → wait.
- Abandoned →
helm rollback <release> -n <ns>. pending-installwith no good revision →helm uninstallthen install again.- Last resort → delete the pending revision Secret, after confirming nothing runs.
- Prevent with
--atomic --timeout 10mand a timeout above your slowest rollout. - Add pipeline concurrency control with
cancel-in-progress: false.
Frequently Asked Questions
How do I know whether the operation is really still running?
Compare the UPDATED timestamp from helm list --all against your Helm timeout. A release pending for longer than the timeout cannot still be progressing under Helm's own supervision. Then check the cluster: kubectl get pods -l app.kubernetes.io/instance=<release> and kubectl rollout status. Pods being created or terminated mean something is genuinely happening and you should wait. A stable set of Pods with an hours-old timestamp means the process that started the operation is gone.
Is it safe to delete the Helm release Secret?
Only as a last resort, after confirming no operation is running. Deleting the pending revision Secret changes Helm's bookkeeping and nothing else: whatever the interrupted operation already applied to the cluster stays applied, so Helm's recorded state and the real state can diverge. The next upgrade reconciles them, which is usually fine but can produce a surprising diff. Try helm rollback first, since from Helm 3.13 it handles this case properly.
What does --atomic do and should I always use it?
--atomic makes Helm roll back automatically if the upgrade fails or times out, so a failed deploy leaves a clean deployed release rather than a stuck pending one. It implies --wait, so Helm blocks until resources are ready. It is a good default for pipelines, with one caveat: the --timeout must exceed your slowest realistic rollout, because otherwise a perfectly healthy but slow deployment is rolled back. The five minute default is frequently too short for large StatefulSets or slow image pulls.
Why did my release get stuck after I cancelled the CI job?
Because cancelling kills the Helm process while the release is marked pending, and nothing then clears the marker. Helm has no background reconciler to notice the operation was abandoned. Prevent it with pipeline concurrency control set to queue rather than cancel, cancel-in-progress: false in GitHub Actions, and by making the job timeout longer than the Helm timeout so the runner cannot kill Helm mid-operation.
Does Helm prevent two concurrent upgrades of the same release?
Not reliably. There is no distributed lock; the pending status written into the release Secret is the only guard, and it is advisory rather than enforced. Two helm upgrade commands issued at nearly the same moment can both proceed, interleaving their changes and producing a release history that does not describe what is actually deployed. Real protection has to come from outside Helm, through pipeline concurrency groups or a deployment queue.