{"record":{"id":"5b2b28b7be1259da","repo":"docker/compose","slug":"fetching-blob-s-w","errorCode":null,"errorMessage":"fetching blob %s: %w","messagePattern":"fetching blob (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/oci/resolver.go","lineNumber":110,"sourceCode":"\tcontent, err := io.ReadAll(fetch)\n\tif err != nil {\n\t\treturn spec.Descriptor{}, nil, err\n\t}\n\treturn descriptor, content, nil\n}\n\n// GetBlob retrieves the content of a blob descriptor (e.g. an artifact layer)\n// from the repository ref belongs to. Unlike Get it doesn't Resolve the\n// digest, as the registry manifests endpoint only serves actual manifests;\n// blob content must be fetched directly from the blobs endpoint.\nfunc GetBlob(ctx context.Context, resolver remotes.Resolver, ref reference.Named, descriptor spec.Descriptor) ([]byte, error) {\n\tfetcher, err := resolver.Fetcher(ctx, ref.String())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"creating fetcher for %s: %w\", ref, err)\n\t}\n\tfetch, err := fetcher.Fetch(ctx, descriptor)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"fetching blob %s: %w\", descriptor.Digest, err)\n\t}\n\tdefer func() { _ = fetch.Close() }()\n\t// bound the read by the declared size so a rogue registry can't cause\n\t// unbounded allocation; the extra byte detects oversized responses.\n\tcontent, err := io.ReadAll(io.LimitReader(fetch, descriptor.Size+1))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"reading blob %s: %w\", descriptor.Digest, err)\n\t}\n\tif int64(len(content)) != descriptor.Size {\n\t\treturn nil, fmt.Errorf(\"blob %s size mismatch: expected %d bytes, got %d\", descriptor.Digest, descriptor.Size, len(content))\n\t}\n\t// GetBlob bypasses containerd's content store, so integrity must be\n\t// checked here before callers write the bytes to disk.\n\tif err := descriptor.Digest.Validate(); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid digest %s: %w\", descriptor.Digest, err)\n\t}\n\tif actual := descriptor.Digest.Algorithm().FromBytes(content); actual != descriptor.Digest {\n\t\treturn nil, fmt.Errorf(\"blob digest mismatch: expected %s, got %s\", descriptor.Digest, actual)","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/docker/compose/blob/ddc4b044b62e9f715212ea4143fa830fac76382f/internal/oci/resolver.go#L92-L128","documentation":"After a Fetcher is obtained, GetBlob calls fetcher.Fetch on the blob descriptor (a GET against the registry's blobs endpoint). A failure here is wrapped with the blob digest: typical causes are 404 (blob missing from the repository, e.g. after registry garbage collection), 401/403 (blob-specific auth), or the descriptor referencing a blob that was never pushed.","triggerScenarios":"Fetching a layer blob whose digest is absent from the repository (deleted by GC, cross-repo mount not completed, partial push), or auth scopes that cover the manifest but not blob access.","commonSituations":"Pulling a compose OCI artifact after the registry garbage-collected unreferenced layers; registries with per-blob token scopes (authenticating for repo A, blob lives in repo B after cross-repo mount); interrupted pushes that uploaded the manifest without all blobs.","solutions":["Re-push the artifact so all layers exist in the repository: docker compose push.","If the registry ran GC, restore/repair the artifact by pushing it again from the source.","Check auth scopes cover blob pulls (pull scope for the target repo).","Verify the digest manually: GET /v2/<repo>/blobs/<digest> with curl to confirm 200 vs 404."],"exampleFix":"# verify blob presence\n$ curl -H \"Authorization: Bearer $TOKEN\" \\\n    https://registry.example.com/v2/my/repo/blobs/sha256:<digest> -o /dev/null -w '%{http_code}\\n'\n# 404 => re-push the artifact","handlingStrategy":"retry","validationCode":"// optional preflight blob existence check\nreq, _ := http.NewRequestWithContext(ctx, http.MethodHead, blobURL(ref, digest), nil)\nif resp, err := http.DefaultClient.Do(req); err == nil && resp.StatusCode == http.StatusNotFound {\n    return fmt.Errorf(\"blob %s missing; re-push the artifact\", digest)\n}","typeGuard":null,"tryCatchPattern":"fetch, err := fetcher.Fetch(ctx, descriptor)\nif err != nil {\n    if isRetryable(err) { // network/5xx\n        time.Sleep(backoff)\n        fetch, err = fetcher.Fetch(ctx, descriptor)\n    }\n    if err != nil {\n        return fmt.Errorf(\"fetching blob %s: %w\", descriptor.Digest, err)\n    }\n}","preventionTips":["Re-push artifacts after registry garbage collection.","Ensure auth scopes cover blob access, not just manifests.","Verify blob presence with a HEAD request when diagnosing."],"tags":["oci","registry","blob","http","pull"],"backgroundTag":null,"analyzedSha":"ddc4b044b62e9f715212ea4143fa830fac76382f","analyzedAt":"2026-08-15T13:31:42.319Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}