Both HelmRelease-generating paths set an action strategy and leave remediation unset. pkg/registry/apps/application/rest.go:1599-1612 and internal/operator/package_reconciler.go:84-98 build Install.Strategy and Upgrade.Strategy with RetryOnFailure and a retry interval, and never populate Remediation. docs/changelogs/v1.4.0.md records leaving it nil as a deliberate part of that change, and pkg/registry/apps/application/rest_timeout_test.go:116-121 pins the shape with a comment calling a re-introduced Remediation{Retries: -1} "a second, conflicting retry mechanism".
The strategy does not govern every branch that decides whether an action runs, and two of them read remediation directly. Line references below are helm-controller v1.5.0, the version this repo installs (internal/fluxinstall/manifests/fluxcd.yaml:8097); they are identical in v1.5.1. actionForState handles an absent release at internal/reconcile/atomic_release.go:389 by calling req.Object.GetInstall().GetRemediation().RetriesExhausted(req.Object) and returning ErrExceededMaxRetries: cannot install release when that is true, with no reference to the strategy at all. The out-of-sync branch consults the upgrade remediation the same way at :409.
A nil pointer does not read as "no policy" there, it reads as a zero-valued one. Install.GetRemediation() returns InstallRemediation{} when Remediation is nil (api/v2/helmrelease_types.go:609-614), so Retries is 0. RetriesExhausted is in.Retries >= 0 && in.GetFailureCount(hr) > int64(in.Retries) (api/v2/helmrelease_types.go:716-718), which with Retries == 0 is true as soon as the recorded install-failure count is at least 1. Setting Retries to -1 makes the first conjunct false, so the branch is never closed.
The reachable shape is a release that carries a non-zero install-failure count and is then found absent. The counter is cleared on a successful reconciliation, so this means a release that has not succeeded since its last failure; the simplest way to reach the absent state deliberately is to delete the release's Helm storage Secret. From there the controller refuses to install rather than installing again, and the retry strategy never comes into play, because the branch that would have led to an install returns the error first.
Worth noting alongside the code: the "second, conflicting retry mechanism" framing in the pinning test does not match this behaviour, since under RetryOnFailure the failed-release branch never reaches the failure counter, and the remediation values only relax the two gates the strategy does not cover. A reader taking that comment as a rule would remove a retry setting that is doing real work.
Both HelmRelease-generating paths set an action strategy and leave remediation unset.
pkg/registry/apps/application/rest.go:1599-1612andinternal/operator/package_reconciler.go:84-98buildInstall.StrategyandUpgrade.StrategywithRetryOnFailureand a retry interval, and never populateRemediation.docs/changelogs/v1.4.0.mdrecords leaving it nil as a deliberate part of that change, andpkg/registry/apps/application/rest_timeout_test.go:116-121pins the shape with a comment calling a re-introducedRemediation{Retries: -1}"a second, conflicting retry mechanism".The strategy does not govern every branch that decides whether an action runs, and two of them read remediation directly. Line references below are helm-controller v1.5.0, the version this repo installs (
internal/fluxinstall/manifests/fluxcd.yaml:8097); they are identical in v1.5.1.actionForStatehandles an absent release atinternal/reconcile/atomic_release.go:389by callingreq.Object.GetInstall().GetRemediation().RetriesExhausted(req.Object)and returningErrExceededMaxRetries: cannot install releasewhen that is true, with no reference to the strategy at all. The out-of-sync branch consults the upgrade remediation the same way at:409.A nil pointer does not read as "no policy" there, it reads as a zero-valued one.
Install.GetRemediation()returnsInstallRemediation{}whenRemediationis nil (api/v2/helmrelease_types.go:609-614), soRetriesis 0.RetriesExhaustedisin.Retries >= 0 && in.GetFailureCount(hr) > int64(in.Retries)(api/v2/helmrelease_types.go:716-718), which withRetries == 0is true as soon as the recorded install-failure count is at least 1. SettingRetriesto -1 makes the first conjunct false, so the branch is never closed.The reachable shape is a release that carries a non-zero install-failure count and is then found absent. The counter is cleared on a successful reconciliation, so this means a release that has not succeeded since its last failure; the simplest way to reach the absent state deliberately is to delete the release's Helm storage Secret. From there the controller refuses to install rather than installing again, and the retry strategy never comes into play, because the branch that would have led to an install returns the error first.
Worth noting alongside the code: the "second, conflicting retry mechanism" framing in the pinning test does not match this behaviour, since under
RetryOnFailurethe failed-release branch never reaches the failure counter, and the remediation values only relax the two gates the strategy does not cover. A reader taking that comment as a rule would remove a retry setting that is doing real work.