# Helm 4.3: what changed and how to upgrade

> Helm 4.3 adds duration template functions, rollback reasons and safer uninstalls, and 4.3.1 fixes CRD waiting. What changed and how to upgrade.

Published: 2026-11-12
Author: k8wiz Team
URL: https://k8wiz.com/articles/helm-4-3-what-changed-and-how-to-upgrade

Helm 4.3.0 came out on September 9, 2026, and the first patch, 4.3.1, followed on October 15. Together they make up the current Helm release. The headline items are small on paper. There are new template functions for durations, a reason field for rollbacks and a history column that shows where a rollback went. The change most likely to surprise you is in `helm uninstall`, which now checks that a resource belongs to the release before deleting it.

The 4.3.1 patch matters too. It fixes a Helm 4 regression where a chart that ships CRDs could fail on install because Helm stopped waiting for the CRDs to be ready. If you install operators with Helm 4 and don't pass `--wait`, that fix alone is a reason to upgrade.

This article walks through each change, what it means for charts and pipelines, and how to move to 4.3.1. Helm 3.22.0 and 3.22.1 shipped on the same days, but they are mostly dependency updates, and the Helm 4.3 release notes state that no further Helm 3 minor releases are planned.

## CRDs are waited for again (4.3.1)

Charts can put CustomResourceDefinitions in a `crds/` directory. Helm installs those first, waits for the API server to accept them, then renders and applies the templates, which may contain custom resources of the new kinds. That wait matters because a CRD isn't usable the instant it's created. The API server has to mark it established before it serves the new kind.

Helm 4 introduced wait strategies. Without the `--wait` flag, the strategy is `hookOnly`, which waits for hooks and skips waiting for everything else. In 4.0 through 4.3.0 that no-op also applied to the CRD step. A plain `helm install` could push the CRDs and immediately try to create custom resources that the API server didn't know about yet. The install then failed with a mapping error for the new kind. Whether it hit depended on how fast the API server was, which made the bug look flaky.

In 4.3.1, when the strategy is `hookOnly`, Helm switches to the `watcher` strategy for the CRD check only. It waits up to 60 seconds for the CRDs, as Helm 3 always did, and the rest of the release still follows `hookOnly`. Nothing changes if you already pass `--wait` or `--rollback-on-failure`, because both already used `watcher`.

If you can't upgrade yet, pass `--wait` on installs of charts with a `crds/` directory. That waits for the whole release, which is slower, but it avoids the race.

```bash
helm install my-operator ./my-operator \
  --namespace operators --create-namespace \
  --wait --timeout 10m
```

Keep in mind that Helm only installs the `crds/` directory on the first install. Upgrades never touch those CRDs, so this fix affects `helm install` and `helm upgrade --install` on a new release, not routine upgrades.

## Uninstall only deletes what the release owns (4.3.0)

Before 4.3, `helm uninstall` read the manifest stored in the release record and deleted every object listed there, unless it carried the `helm.sh/resource-policy: keep` annotation. It didn't check whether the live object still belonged to the release.

That gap bites when ownership moves. Helm 3.17 and later have `--take-ownership` on `helm install` and `helm upgrade`, which lets a second release adopt an object the first one created. Teams also split charts, move a ConfigMap from one release to another, or recreate an object by hand. In all of these cases the old release's manifest still lists the object, and uninstalling the old release deleted something another release now depended on.

Helm 4.3 fetches each object before deleting it and checks the same ownership metadata Helm writes on every install and upgrade. The object must have the label `app.kubernetes.io/managed-by: Helm`, and the annotations `meta.helm.sh/release-name` and `meta.helm.sh/release-namespace` must match the release being removed. Objects that pass are deleted. Objects that fail are skipped and listed in the output.

```text
1 resource(s) were not deleted because they are not owned by this release:
[ConfigMap] web-feature-flags

release "web" uninstalled
```

There is a third case. If Helm can't read the object at all, for example because the service account running the uninstall has `delete` permission but not `get`, the ownership can't be verified. Helm 4.3 skips those objects too and prints the error next to each one. This is the change to check in your pipelines. A CI role trimmed to create and delete permissions used to uninstall cleanly and now leaves objects behind. Grant `get` on the kinds your charts manage.

To see the decision before it happens, run the uninstall as a dry run. It logs a warning for every object it would skip and why.

```bash
helm uninstall web -n shop --dry-run
```

When a skipped object surprises you, look at its ownership metadata directly.

```bash
kubectl get configmap web-feature-flags -n shop \
  -o jsonpath='{.metadata.labels.app\.kubernetes\.io/managed-by}{" "}{.metadata.annotations.meta\.helm\.sh/release-name}{" "}{.metadata.annotations.meta\.helm\.sh/release-namespace}{"\n"}'
```

If the release name printed there is a different release, the new behavior just saved you from an outage. If the label or annotations are missing because some other tool rewrote the object, delete it yourself once you're sure nothing uses it.

## Rollbacks can carry a reason (4.3.0)

`helm rollback` gained a `--description` flag. Until now every rollback revision got the description `Rollback to N`, which tells you where the release went but not why. When someone reads the history at 3 a.m. during the next incident, the why is what they need.

```bash
helm rollback web 4 -n shop \
  --description "Revert 2.4.0: checkout p99 latency doubled" \
  --wait --timeout 10m
```

The description is limited to 256 characters, and Helm rejects a longer one before doing anything. If you leave the flag out, you get the old `Rollback to N` text.

A custom description replaces that text, so the target revision would disappear from the history. To keep it, 4.3 also stores the target revision as a separate field on the release, `rollback_revision`. The table output hides it unless you ask for the new column.

```bash
helm history web -n shop --show-rollback-revision
```

```text
REVISION  UPDATED                   STATUS      CHART      APP VERSION  ROLLBACK  DESCRIPTION
4         Tue Nov  3 16:40:02 2026  superseded  web-1.4.1  2.3.1                  Upgrade complete
5         Wed Nov  4 09:05:17 2026  superseded  web-1.5.0  2.4.0                  Upgrade complete
6         Wed Nov  4 11:31:50 2026  deployed    web-1.4.1  2.3.1        4         Revert 2.4.0: checkout p99 latency doubled
```

JSON and YAML output include `rollback_revision` for rollback revisions without any flag, which is the easier format for scripts and dashboards. The field is new in 4.3, so rollbacks made with an older Helm leave it empty. For those, the description is still the only record.

## Duration functions in templates (4.3.0)

Charts often take a time value once and need it in several units. A shutdown timeout ends up in `terminationGracePeriodSeconds` as an integer of seconds, in an environment variable in milliseconds, and maybe in a flag as a Go duration string. Before 4.3 you either asked users for each unit separately or did the arithmetic with Sprig math functions and hoped nobody wrote `"1m"`.

Helm 4.3 adds a family of duration helpers to the template engine. `durationSeconds`, `durationMinutes`, `durationHours`, `durationDays` and `durationWeeks` return floats. `durationMilliseconds`, `durationMicroseconds` and `durationNanoseconds` return integers. `durationRoundTo` and `durationTruncateTo` round a duration to a multiple of another, and `mustToDuration` parses a value into a duration or fails the render.

The input is flexible. A string such as `"90s"` or `"1h30m"` is parsed as a Go duration. A bare number, or a numeric string such as `"2.5"`, is treated as seconds. That means a values file can accept both `shutdownTimeout: 45` and `shutdownTimeout: "45s"`.

```yaml
# values.yaml
shutdownTimeout: "45s"
cacheTTL: "2h"
```

```yaml
# templates/deployment.yaml (excerpt)
spec:
  template:
    spec:
      terminationGracePeriodSeconds: {{ .Values.shutdownTimeout | mustToDuration | durationSeconds | int }}
      containers:
        - name: app
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          env:
            - name: CACHE_TTL_MS
              value: {{ .Values.cacheTTL | durationMilliseconds | quote }}
```

The order in the first line matters. Every helper except `mustToDuration` returns 0 when it can't parse its input, with no error. A typo like `"45 s"` would render `terminationGracePeriodSeconds: 0`, and Kubernetes would kill your pods instantly. Putting `mustToDuration` first turns that typo into a failed render. The `int` at the end converts the float to the integer Kubernetes expects.

These functions only exist in Helm 4.3 and later. A chart that uses them fails to render on Helm 3 and on earlier Helm 4 releases with an unknown function error. Chart.yaml has no field for a minimum Helm version, so state it in the chart's README and hold off on publishing such a chart to a public repository until your users have upgraded.

## Smaller fixes worth knowing

A few other items in 4.3.0 can explain odd behavior you may have seen on 4.2.

The chart loader no longer drops a values file whose size ends exactly on a 4096-byte boundary. It's rare, but a values file that silently vanished from a chart is a miserable thing to debug, so if you ever saw that, this was the cause.

The `watcher` wait strategy got faster on releases with many objects. The release notes describe concurrent status computation to avoid multi-minute delays, and a resync period lowered from one hour to 3 minutes.

`helm package --sign` and `helm verify` now accept a GnuPG keybox file (`pubring.kbx`) as the keyring. Recent GnuPG versions store keys in that format by default, so you no longer need to export a legacy `pubring.gpg` first.

When a `lookup` call in a template returns nothing, Helm 4.3 logs that at debug level. Run with `--debug` when a template behaves as if an object doesn't exist and you need to know whether the lookup ran against the cluster at all.

## Upgrading to 4.3.1

The 4.3 release notes list no breaking changes, and the chart format is unchanged, so the upgrade itself is a binary swap. The official install script takes a version, which is the safer choice in CI than whatever is latest on the day the job runs.

```bash
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4
chmod 700 get_helm.sh
./get_helm.sh --version v4.3.1
helm version --short
```

The script installs into `/usr/local/bin` and uses `sudo` when needed. Pass `--no-sudo` and set `HELM_INSTALL_DIR` to put the binary somewhere you own.

Before rolling it out to every pipeline, check three things. First, any automation that uninstalls releases needs `get` permission on the objects it deletes, or objects will start to pile up. Second, scripts that parse `helm uninstall` output should expect the new lines listing skipped objects. Third, if you plan to use the duration functions, upgrade every Helm that renders your charts first, including tools that embed the Helm library to render charts, such as Argo CD and Flux.

The release notes schedule Helm 4.4.0 for January 13, 2027. Until then 4.3.1 is the version to run, and if your charts ship CRDs it's the first Helm 4 release where you can drop the `--wait` workaround.
