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
- Ensure the input is a real 'docker save' / 'podman image save' tarball: 'docker save <img> -o img.tar' and pass img.tar
- Inspect the archive before loading: 'tar -xOf img.tar manifest.json' and confirm it is a JSON array like [{"Config":..., "Layers":[...]}]
- If the input is gzip-compressed, wrap it in a gzip.Reader before passing it to NewImageArchive
- 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
- Always pre-check the input is a docker-save tarball ('tar -tf x.tar manifest.json')
- Decompress gzip inputs before passing readers to dive
- Never pass arbitrary user-supplied tars straight into NewImageArchive without the recover wrapper
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
- failed to unmarshal docker config: %w
- could not find '%s' in parsed layers
- cannot export analysis: %w
- directory for JSON export does not exist: %s
- could not find image manifest
AI-assisted analysis of wagoodman/dive@d6c691947f (2026-08-15).
Data as JSON: /api/errors/beaade1e5c3d1559.
Report an issue: GitHub.