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

  1. Consume the first bytes and confirm gzip magic 0x1f 0x8b before calling LoadArchive; log them on failure.
  2. Fix the upstream source: correct URL/OCI reference so the body is a real gzip tgz.
  3. If the body may be base64-encoded, decode it before passing the reader.
  4. 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

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


AI-assisted analysis of helm/helm@2a29f1770b (2026-08-15). Data as JSON: /api/errors/996b28ccf7d98dbb. Report an issue: GitHub.