Repository navigation
Description
Every HelmRelease the platform generates retries a failed install or upgrade forever, and nothing in the policy can tell a release that is slow from one that will never succeed.
Both generators, pkg/registry/apps/application/rest.go and internal/operator/package_reconciler.go, set Install.Strategy and Upgrade.Strategy to RetryOnFailure with an explicit RetryInterval, defaulting to 30s from pkg/cmd/server/start.go:90. Neither sets Remediation, so there is no retry count and no exhaustion. A release whose failure is permanent re-enters its wait 30 seconds after each timeout, for as long as the object exists.
For a transient cause that is exactly right, and it is why the setting exists. The comment at rest.go:1580 records the reasoning: RetryOnFailure with an explicit interval was chosen instead of Remediation{Retries: -1} precisely to decouple retry timing from Spec.Interval, because the previous coupling made failed installs retry only every five minutes and blew past E2E budgets. Any fix here has to keep that property, so reaching back for a remediation-based cap would undo it.
What is missing is the other half. A release that cannot succeed, for instance one whose readiness gate waits on an object the storage layer will never be able to create, is indistinguishable from one that is merely slow, and the policy treats both the same forever. The cost is not only the retry itself: each attempt re-enters a wait that occupies one of the shard's helm-controller slots for its whole window, which is what turns a permanent failure into near-continuous occupancy rather than a one-off. That side is #4145, and this is the other lever on the same symptom; either one alone leaves the other standing.
Worth deciding together rather than separately, because the options trade against each other. A retry cap bounds the damage but makes the platform stop converging on a cause that later clears itself. Backoff keeps convergence and reduces occupancy without a cliff. Detecting terminal failure is the most precise and the most work, and helm-controller already has the notion, since it stalls a release with a terminal error for an uncompilable CEL expression; the distinction exists in the controller and simply is not reachable from a wait timeout.
Nothing here measures how often a permanently failing release actually occurs in a running cluster, and that number is what should decide how much of this is worth building.
Activity
scooby87 commentedon Oct 26, 2026
Opened #5109 with one concrete, opt-in option from this issue (a per-kind single-attempt policy with a terminal
Stalledstatus, requested for the 1.6 line).kvaps commentedon Oct 26, 2026
We discussed this internally. I'd keep retries unbounded, endless reconciliation is how Kubernetes is supposed to work. The problem is the fixed 30s loop, so I'd replace it with exponential backoff and move the retry decision from the HelmRelease spec to flux-plunger:
Strategy, noRemediation. This isRemediateOnFailurewithretries: 0andremediateLastFailure: false, so one attempt, no uninstall, no rollback (atomic_release.go:540). The release stalls withStalled=True/RetriesExceeded.reconcile.fluxcd.io/requestedAt+reconcile.fluxcd.io/forceAtwith the same token. Flux handles it even when retries are exhausted, for install, upgrade and failed releases (atomic_release.go:424,:444,:533).min(30s * 2^(n-1), cap), wherenis Flux's owninstallFailures/upgradeFailures. Annotations don't change the generation, so Flux doesn't reset these counters and plunger needs no state of its own.reset.go), so a new desired state gets a fast first attempt. Suspended releases are not touched.Plunger never writes the spec, so it doesn't fight with the generators, and the first retry is still 30s, so E2E is not affected. It also reduces slot occupancy for #4145, and makes #5109 simple: an Application annotation that disables retries in plunger leaves the release in terminal
Stalledstate.Needs to be checked on a stand: retrying a failed first install goes through "forcing upgrade for failed release" and may hit
has no deployed releases. Plunger already handles that, but the retry may take two steps.feat(api): expose HelmRelease failure state on Application status (#5171