helm/helm · error

failed to extract archive: %w

Error message

failed to extract archive: %w

What it means

TarGzExtractor.Extract failed while unpacking the local tarball into the temp dir. Distinct inner causes: gzip.NewReader error (file is not gzip — e.g. a zip renamed to .tgz), tar stream error mid-read (truncation), cleanJoin rejecting an entry name ('..', ':', absolute path), an entry typeflag outside TypeDir/TypeReg (symlinks and hardlinks hit the 'unknown type' branch and are rejected), or target-file creation failures (permissions, ENOSPC).

Source

Thrown at internal/plugin/installer/local_installer.go:146

	provSource := i.Source + ".prov"
	if provData, err := os.ReadFile(provSource); err == nil {
		provPath := tarballPath + ".prov"
		if err := os.WriteFile(provPath, provData, 0o644); err != nil {
			slog.Debug("failed to save provenance file", "error", err)
		}
	}

	// Create a temporary directory for extraction
	tempDir, err := os.MkdirTemp("", "helm-plugin-extract-")
	if err != nil {
		return fmt.Errorf("failed to create temp directory: %w", err)
	}
	defer os.RemoveAll(tempDir)

	// Extract the archive
	buffer := bytes.NewBuffer(data)
	if err := i.extractor.Extract(buffer, tempDir); err != nil {
		return fmt.Errorf("failed to extract archive: %w", err)
	}

	// Plugin directory should be named after the plugin at the archive root
	pluginName := stripPluginName(filepath.Base(i.Source))
	pluginDir := filepath.Join(tempDir, pluginName)
	if _, err = os.Stat(filepath.Join(pluginDir, "plugin.yaml")); err != nil {
		return fmt.Errorf("plugin.yaml not found in expected directory %s: %w", pluginDir, err)
	}

	// Copy to the final destination
	slog.Debug("copying", "source", pluginDir, "path", i.Path())
	return fs.CopyDir(pluginDir, i.Path())
}

// Update updates a local repository
func (i *LocalInstaller) Update() error {
	slog.Debug("local repository is auto-updated")
	return nil

View on GitHub (pinned to 2a29f1770b)

Solutions

  1. Inspect entries: tar -tvf myplugin-1.0.0.tgz — look for 'l' (symlink) or 'h' (hardlink) types and for names starting with / or containing ..
  2. Repackage with regular files only and relative paths: cd build && tar -czf ../myplugin-1.0.0.tgz myplugin/
  3. Confirm the file is genuinely gzip: file myplugin-1.0.0.tgz
  4. Ensure TMPDIR is writable and has space

Example fix

# before: macOS tar keeps symlink
ln -s ../shared/binary myplugin/bin && tar -czf myplugin-1.0.0.tgz myplugin
# after: real file, relative paths
cp ../shared/binary myplugin/bin && tar -czf myplugin-1.0.0.tgz myplugin
Defensive patterns

Strategy: validation

Validate before calling

// Reject archives that TarGzExtractor cannot handle before installing: only dirs and regular files, safe relative names.
func extractionSafeTgz(p string) error {
	f, _ := os.Open(p)
	defer f.Close()
	gz, err := gzip.NewReader(f)
	if err != nil {
		return fmt.Errorf("not gzip: %w", err)
	}
	tr := tar.NewReader(gz)
	for {
		h, err := tr.Next()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			return err
		}
		if h.Typeflag != tar.TypeDir && h.Typeflag != tar.TypeReg && h.Typeflag != tar.TypeXHeader && h.Typeflag != tar.TypeXGlobalHeader {
			return fmt.Errorf("entry %s has unsupported type %b (symlinks/hardlinks not supported)", h.Name, h.Typeflag)
		}
		if path.IsAbs(h.Name) || strings.Contains(h.Name, ":") || strings.Contains(filepath.ToSlash(h.Name), "..") {
			return fmt.Errorf("entry %s has an unsafe name", h.Name)
		}
	}
}

Prevention

When it happens

Trigger: helm plugin install ./myplugin-1.0.0.tgz where the archive was created on macOS with symlinks (tar preserves them as TypeSymlink entries, unsupported here); archive contains ./ or absolute entries; truncated file; read-only TMPDIR.

Common situations: macOS/Linux packaging differences (symlinked binaries inside plugin dirs); files zipped then renamed; incomplete scp/ftp transfers.

Related errors


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