registry.Client.Tags returns normalized semver strings, not the registry's actual tags
- Dominant language
- Go
- Stars
- 30.2k
- Forks
- 7.8k
- Avg merge
- 20h 58m
- Merged PRs (30d)
- 36
Description
### What happened?
`registry.Client.Tags` returns *normalized* semver strings rather than the tag strings as they exist in the registry. The returned value is `tv.String()`:
https://github.com/helm/helm/blob/main/pkg/registry/client.go#L817
Since these values are fed straight back into a pull reference (`findChartURL` builds `oci://repo/name:version` from the resolved version, [manager.go#L726](https://github.com/helm/helm/blob/main/pkg/downloader/manager.go#L726)), any tag whose normalized form differs from its stored form can be *resolved* but not *pulled*.
The clearest case is the underscore convention from #10166. `Tags` converts `_` back to `+` before parsing:
https://github.com/helm/helm/blob/main/pkg/registry/client.go#L799
```go
tagVersion, err := semver.StrictNewVersion(strings.ReplaceAll(tag, "_", "+"))
```
…but the conversion is one-directional. A chart stored under tag `1.1.17_meta` is returned as `1.1.17+meta`, which is not a legal OCI tag and does not exist in the registry.
Related: `StrictNewVersion` silently drops tags it cannot parse — `v1.1.17` and `1.1` are both rejected with no log line — so a chart can be invisible to `helm dep update` with no diagnostic at all. Whether `v`-prefixed tags *should* be accepted is a separate design question, but if they are accepted, the original tag string has to be preserved or the pull will 404 for the same reason.
### What did you expect to happen?
`Tags` returns tag strings that can actually be used as OCI references — i.e. the original tag as stored in the registry — while still sorting by semver precedence. Tags that are skipped are logged at debug level rather than dropped silently.
### How can we reproduce it (as minimally and precisely as possible)?
1. Push a chart to an OCI registry under tag `1.1.17_meta` (the `_` form Helm itself documents for build metadata, per #10166).
2. Reference it from a parent chart with a constraint:
```yaml
dependencies:
- name: my-subchart
repository: "oci://my-registry"
version: "1.x.x"
```
3. `helm dep update` resolves the version to `1.1.17+meta` and then fails to pull it — that tag does not exist in the registry.
Suggested fix: carry the original tag alongside the parsed version inside `Tags` (e.g. sort a `[]struct{orig string; v *semver.Version}` and return `orig`), so sorting stays semver-correct but the returned strings remain valid references. Note `Tags` is exported v4 API, so the return *shape* shouldn't change — only the strings it contains.
Found while investigating #32581.
### Helm version
```console
main @ d62bee21c (v4.2.4+)
```
### Kubernetes version
Unrelated
Contributor guide
Research direction
Read pkg/registry/client.go around Tags and pkg/downloader/manager.go around findChartURL, then trace how resolved versions become OCI references. Preserve each registry tag for returned references while retaining semver ordering, and add debug logging for skipped tags; verify that the underscore-form tag resolves and can be pulled.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend, cli
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100