Skip to content

Generated HelmReleases set RetryOnFailure with a nil Remediation, which can refuse to reinstall an absent release #3554

Description

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.

Activity

  1. added
    kind/bugCategorizes issue or PR as related to a bug
    area/platformIssues or PRs related to platform infrastructure (bundle, flux, talos, installer)
    area/apiIssues or PRs related to the cozystack-api aggregated API server
    triage/acceptedIndicates an issue is ready to be actively worked on
    priority/backlogGeneral backlog priority. Lower than priority/important-longterm
    and removed
    triage/needs-triageIndicates an issue needs triage by a maintainer
    on Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/apiIssues or PRs related to the cozystack-api aggregated API serverarea/platformIssues or PRs related to platform infrastructure (bundle, flux, talos, installer)kind/bugCategorizes issue or PR as related to a bugpriority/backlogGeneral backlog priority. Lower than priority/important-longtermtriage/acceptedIndicates an issue is ready to be actively worked on

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions