containerd/containerd · error · errdefs.ErrInvalidArgument

unable to fetch descriptor (%s) which reports content size o

Error message

unable to fetch descriptor (%s) which reports content size of zero: %w

What it means

Fetch rejects a descriptor that reports Size 0, because a zero-length blob cannot be meaningfully committed to the content store and almost always indicates the registry responded without a Content-Length header (poorly configured front end or proxy). Throwing early here avoids a more confusing downstream error.

Source

Thrown at core/remotes/handlers.go:156

func Fetch(ctx context.Context, ingester content.Ingester, fetcher Fetcher, desc ocispec.Descriptor) error {
	log.G(ctx).Debug("fetch")

	cw, err := content.OpenWriter(ctx, ingester, content.WithRef(MakeRefKey(ctx, desc)), content.WithDescriptor(desc))
	if err != nil {
		return err
	}
	defer cw.Close()

	ws, err := cw.Status()
	if err != nil {
		return err
	}

	if desc.Size == 0 {
		// most likely a poorly configured registry/web front end which responded with no
		// Content-Length header; unable (not to mention useless) to commit a 0-length entry
		// into the content store. Error out here otherwise the error sent back is confusing
		return fmt.Errorf("unable to fetch descriptor (%s) which reports content size of zero: %w", desc.Digest, errdefs.ErrInvalidArgument)
	}
	if ws.Offset == desc.Size {
		// If writer is already complete, commit and return
		err := cw.Commit(ctx, desc.Size, desc.Digest)
		if err != nil && !errdefs.IsAlreadyExists(err) {
			return fmt.Errorf("failed commit on ref %q: %w", ws.Ref, err)
		}
		return err
	}

	if desc.Size == int64(len(desc.Data)) {
		return content.Copy(ctx, cw, bytes.NewReader(desc.Data), desc.Size, desc.Digest)
	}

	rc, err := fetcher.Fetch(ctx, desc)
	if err != nil {
		return err
	}

View on GitHub (pinned to 4246446a2b)

Solutions

  1. Fix the registry/proxy to return Content-Length headers for GETs (disable chunked re-encoding)
  2. Check for proxies like nginx stripping headers; ensure Content-Length is passed through
  3. Verify the image descriptor is valid in the source registry (re-pull manifest)
  4. Use a registry that sets Content-Length correctly (Docker Hub, Harbor, recent distribution releases)

Example fix

// before: nginx proxy strips headers
// proxy_pass http://registry;
// after: pass Content-Length through
location /v2/ {
    proxy_pass http://registry;
    proxy_set_header Content-Length $content_length;
}
Defensive patterns

Strategy: validation

Validate before calling

if desc.Size == 0 {
    // fix registry/proxy config before fetching
    return errors.New("descriptor reports zero size: registry not returning Content-Length")
}

Type guard

func hasValidSize(desc ocispec.Descriptor) bool {
    return desc.Size > 0
}

Try / catch

err := handlers.Fetch(ctx, ingester, fetcher, desc)
if err != nil && strings.Contains(err.Error(), "content size of zero") {
    // fix registry/proxy; try direct registry access bypassing front end
    return fetchDirect(ctx, desc)
}

Prevention

When it happens

Trigger: Calling Fetch (via HandlerFunc/handlers) with a workspace descriptor whose desc.Size == 0 — typically when the registry or a web front end omitted Content-Length for the manifest/blob request.

Common situations: Registries behind proxies/CDNs that strip Content-Length; misconfigured reverse proxies; chunked responses to manifest GETs; self-hosted registries misconfigured with wrong headers.

Related errors


AI-assisted analysis of containerd/containerd@4246446a2b (2026-09-02). Data as JSON: /api/errors/b38bbd86d06b213e. Report an issue: GitHub.