helm/helm · error
stream does not appear to be a valid chart file (details: %w
Error message
stream does not appear to be a valid chart file (details: %w)
What it means
Stream variant of the bad-archive error, thrown by loader.LoadArchive(io.Reader) — the SDK entry point used by tools such as Flux. archive.LoadArchiveFiles returned an error matching gzip.ErrHeader, meaning the byte stream does not start with a valid gzip header, so it cannot be a chart tarball.
Source
Thrown at pkg/chart/loader/load.go:166
case c3.APIVersionV3:
return c3load.Load(name)
default:
return nil, errors.New("unsupported chart version")
}
}
}
return nil, errors.New("unable to detect chart version, no Chart.yaml found")
}
// LoadArchive loads from a reader containing a compressed tar archive.
func LoadArchive(in io.Reader) (chart.Charter, error) {
// Note: This function is for use by SDK users such as Flux.
files, err := archive.LoadArchiveFiles(in)
if err != nil {
if errors.Is(err, gzip.ErrHeader) {
return nil, fmt.Errorf("stream does not appear to be a valid chart file (details: %w)", err)
}
return nil, fmt.Errorf("unable to load chart archive: %w", err)
}
for _, f := range files {
if f.Name == "Chart.yaml" {
c := new(chartBase)
if err := yaml.Unmarshal(f.Data, c); err != nil {
return c, fmt.Errorf("cannot load Chart.yaml: %w", err)
}
switch c.APIVersion {
case c2.APIVersionV1, c2.APIVersionV2, "":
return c2load.LoadFiles(files)
case c3.APIVersionV3:
return c3load.LoadFiles(files)
default:
return nil, errors.New("unsupported chart version")
}View on GitHub (pinned to 2a29f1770b)
Solutions
- Consume the first bytes and confirm gzip magic 0x1f 0x8b before calling LoadArchive; log them on failure.
- Fix the upstream source: correct URL/OCI reference so the body is a real gzip tgz.
- If the body may be base64-encoded, decode it before passing the reader.
- Make sure nothing reads the stream before LoadArchive; if you need peeking, use bufio.Reader and pass the same bufio.Reader (it replays the peeked bytes).
Example fix
// before
ch, err := loader.LoadArchive(resp.Body) // resp.Body was an HTML error page
// after
br := bufio.NewReader(resp.Body)
if magic, _ := br.Peek(2); len(magic) < 2 || magic[0] != 0x1f || magic[1] != 0x8b {
body, _ := io.ReadAll(io.LimitReader(br, 256))
return fmt.Errorf("not a gzip chart stream, got %q", body)
}
ch, err := loader.LoadArchive(br) Defensive patterns
Strategy: validation
Validate before calling
// Peek gzip magic on the stream before LoadArchive
br := bufio.NewReader(src)
head, _ := br.Peek(2)
if len(head) == 2 && head[0] == 0x1f && head[1] == 0x8b {
ch, err := loader.LoadArchive(br) // same reader: peeked bytes are replayed
} else {
err = fmt.Errorf("source did not return a gzip chart stream")
} Try / catch
if _, err := loader.LoadArchive(r); err != nil {
if strings.Contains(err.Error(), "does not appear to be a valid chart file") {
// body is not a chart: log response snippet, fix the source URL/reference
}
} Prevention
- Peek before parsing when the remote source is untrusted.
- Check HTTP status explicitly; do not parse error bodies as charts.
- In Flux-style controllers, validate the artifact digest before reconciliation.
When it happens
Trigger: Passing LoadArchive a reader over an uncompressed tar, a base64 body that was never decoded, an OCI manifest JSON, an HTTP error page, or a stream whose first bytes were already consumed by earlier code (e.g. peeking with bufio.Reader and discarding).
Common situations: GitOps controllers (Flux) reconciling a chart URL that returns HTML/JSON; SDK users double-reading a request body; proxies injecting an error page; wrong artifact pulled from a registry.
Related errors
- cannot load a directory
- file '%s' does not appear to be a valid chart file (details:
- unsupported chart type
- YAML path cannot be empty
- unsupported chart dependency type
AI-assisted analysis of helm/helm@2a29f1770b (2026-08-15).
Data as JSON: /api/errors/996b28ccf7d98dbb.
Report an issue: GitHub.