wagoodman/dive · error

unable to extract from image '%s': %+v

Error message

unable to extract from image '%s': %+v

What it means

Returned by engineResolver.Extract (dive/image/docker/engine_resolver.go:66) when ExtractFromImage fails for a docker-engine image. Note a defect in this region: the code shadows the error in `if err := ExtractFromImage(...); err == nil` and then formats the outer stale `err` from fetchArchive (already nil-checked), so the message's %+v detail is always '<nil>' and the real cause is lost. The failure itself still comes from ExtractFromImage (e.g. missing layer path, tar processing error, write failures to the destination).

Source

Thrown at dive/image/docker/engine_resolver.go:66

func (r *engineResolver) Build(ctx context.Context, args []string) (*image.Image, error) {
	id, err := buildImageFromCli(afero.NewOsFs(), args)
	if err != nil {
		return nil, err
	}
	return r.Fetch(ctx, id)
}

func (r *engineResolver) Extract(ctx context.Context, id string, l string, p string) error {
	reader, err := r.fetchArchive(ctx, id)
	if err != nil {
		return err
	}

	if err := ExtractFromImage(io.NopCloser(reader), l, p); err == nil {
		return nil
	}

	return fmt.Errorf("unable to extract from image '%s': %+v", id, err)
}

func (r *engineResolver) fetchArchive(ctx context.Context, id string) (io.ReadCloser, error) {
	var err error
	var dockerClient *client.Client

	host, err := determineDockerHost()
	if err != nil {
		return nil, fmt.Errorf("could not determine docker host: %v", err)
	}
	clientOpts := []client.Opt{client.FromEnv}
	clientOpts = append(clientOpts, client.WithHost(host))

	switch strings.Split(host, ":")[0] {
	case "ssh":
		helper, err := connhelper.GetConnectionHelper(host)
		if err != nil {
			return nil, fmt.Errorf("failed to get docker connection helper: %w", err)

View on GitHub (pinned to d6c691947f)

Solutions

  1. Update dive - newer releases fix the error shadowing so the real cause is reported
  2. Sanity-check the selector against the image: confirm the layer/file path exists via the dive UI before extracting
  3. Check the destination: writable, not full (df -h), correct permissions
  4. Fall back to native tooling to confirm the data is extractable: docker save + tar -xf

Example fix

// upstream bug worth fixing if you fork dive:
// before
if err := ExtractFromImage(io.NopCloser(reader), l, p); err == nil {
    return nil
}
return fmt.Errorf("unable to extract from image '%s': %+v", id, err) // err is always nil

// after
if err := ExtractFromImage(io.NopCloser(reader), l, p); err != nil {
    return fmt.Errorf("unable to extract from image '%s': %w", id, err)
}
return nil
Defensive patterns

Strategy: try-catch

Validate before calling

// confirm the selector matches something before extracting
// (use the dive UI/file listing to verify layer + file paths exist for the image)
exists, err := imageHasPath(img, layerPath)
if err != nil || !exists {
    return fmt.Errorf("nothing to extract at %s", layerPath)
}

Try / catch

err := resolver.Extract(ctx, id, layer, dest)
if err != nil {
    if strings.Contains(err.Error(), "unable to extract from image") {
        // note: this dive version reports <nil> detail (error-shadowing bug); diagnose independently:
        // 1) dest writable? df -h dest  2) layer selector valid? 3) engine healthy? docker info
        return fmt.Errorf("extract failed (cause hidden by dive bug): %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Extracting with a layer/file selector that does not match anything in the fetched archive; the engine returning an incomplete archive; destination write errors; any ExtractFromImage failure - and because of the shadowing bug you get no diagnostic detail, only the image id.

Common situations: Export workflows that pass a file filter or destination the layer doesn't contain; disk-full or permission-denied on the export directory; dive versions carrying this bug make triage hard since the cause prints as <nil>.

Related errors


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