wagoodman/dive · critical

failed to unmarshal manifest: %w

Error message

failed to unmarshal manifest: %w

What it means

newManifest unmarshals manifest.json from a docker image archive and panics if json.Unmarshal fails. The expected shape is a JSON array of manifests of which element [0] is taken, so any non-array JSON (or invalid JSON at all) aborts the process instead of returning an error. Because it is a panic (not a returned error), it will crash the caller unless recovered.

Source

Thrown at dive/image/docker/manifest.go:18

package docker

import (
	"encoding/json"
	"fmt"
)

type manifest struct {
	ConfigPath    string   `json:"Config"`
	RepoTags      []string `json:"RepoTags"`
	LayerTarPaths []string `json:"Layers"`
}

func newManifest(manifestBytes []byte) manifest {
	var manifest []manifest
	err := json.Unmarshal(manifestBytes, &manifest)
	if err != nil {
		panic(fmt.Errorf("failed to unmarshal manifest: %w", err))
	}
	return manifest[0]
}

View on GitHub (pinned to d6c691947f)

Solutions

  1. Ensure the input is a real 'docker save' / 'podman image save' tarball: 'docker save <img> -o img.tar' and pass img.tar
  2. Inspect the archive before loading: 'tar -xOf img.tar manifest.json' and confirm it is a JSON array like [{"Config":..., "Layers":[...]}]
  3. If the input is gzip-compressed, wrap it in a gzip.Reader before passing it to NewImageArchive
  4. Wrap calls in a defer/recover (see defense) because this failure mode is a panic, not an error value

Example fix

// before
func load(f io.Reader) {
    img, err := docker.NewImageArchive(f) // panics inside on bad manifest.json
    ...
}

// after: guard against the panic at the call site
func load(f io.Reader) (img *docker.ImageArchive, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("invalid image archive (bad manifest.json): %v", r)
        }
    }()
    return docker.NewImageArchive(f)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-validate that the payload contains a manifest.json that is a non-empty JSON array.
func hasValidManifest(r io.ReaderAt) bool {
    tr := tar.NewReader(io.NewSectionReader(r, 0, r.Size()))
    for {
        h, err := tr.Next()
        if err == io.EOF { return false }
        if err != nil { return false }
        if path.Clean(h.Name) == "manifest.json" {
            var m []map[string]any
            return json.NewDecoder(tr).Decode(&m) == nil && len(m) > 0
        }
    }
}

Try / catch

// This failure is a panic inside the library, so guard with defer/recover at the call boundary.
func safeNewImageArchive(f io.Reader) (img *docker.ImageArchive, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("invalid image archive (manifest.json): %v", r)
        }
    }()
    return docker.NewImageArchive(f)
}

Prevention

When it happens

Trigger: Calling NewImageArchive on a reader whose payload is not a well-formed 'docker save' archive: manifest.json missing, empty, truncated, or a single JSON object instead of an array (some tools and hand-crafted archives emit {}). Note also that an empty array '[]' passes Unmarshal but then panics on manifest[0] with an index-out-of-range.

Common situations: Feeding dive a random tarball that is not a docker-save archive (e.g. a source tarball, an OCI layout directory tarred up); partial downloads; archives generated by tools that write a different manifest schema; piping a compressed stream (gzip) where the reader expects raw tar.

Related errors


AI-assisted analysis of wagoodman/dive@d6c691947f (2026-08-15). Data as JSON: /api/errors/beaade1e5c3d1559. Report an issue: GitHub.