{"id":"eaa62c8f82bb68e4","repo":"gohugoio/hugo","slug":"failed-to-decode-gif-w","errorCode":null,"errorMessage":"failed to decode gif: %w","messagePattern":"failed to decode gif: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"resources/images/codec.go","lineNumber":189,"sourceCode":"\t\tlogg.StringFunc(\n\t\t\tfunc() string {\n\t\t\t\treturn fmt.Sprintf(\"Decoding image from format %s\", f)\n\t\t\t},\n\t\t),\n\t)\n\tswitch f {\n\tcase JPEG, PNG:\n\t\t// We reworked this decode/encode setup to get full WebP support in v0.153.0.\n\t\t// In the first take of that we used f to decide whether to call png.Decode or jpeg.Decode here,\n\t\t// but testing it on some sites, it seems that it's not uncommon to store JPEGs with PNG extensions and vice versa.\n\t\t// So, to reduce some noise in that release, we fallback to the standard library here,\n\t\t// which will read the magic bytes and decode accordingly.\n\t\timg, _, err := image.Decode(r)\n\t\treturn img, err\n\tcase GIF:\n\t\tg, err := gif.DecodeAll(r)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to decode gif: %w\", err)\n\t\t}\n\t\tif len(g.Delay) > 1 {\n\t\t\treturn &giphy{gif: g, Image: g.Image[0]}, nil\n\t\t}\n\t\treturn g.Image[0], nil\n\tcase TIFF:\n\t\treturn tiff.Decode(r)\n\tcase BMP:\n\t\treturn bmp.Decode(r)\n\tcase AVIF:\n\t\treturn d.avif.Decode(r)\n\tcase WEBP:\n\t\timg, err := d.webp.Decode(r)\n\t\tif err == nil {\n\t\t\treturn img, nil\n\t\t}\n\t\tif rs, ok := r.(io.ReadSeeker); ok {\n\t\t\t// See issue 14288. Turns out it's not uncommon to e.g. name their PNG files with a WEBP extension.","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/gohugoio/hugo/blob/8a468df065a75c1c7cf9f6850f32148746590ea5/resources/images/codec.go#L171-L207","documentation":"When decoding a resource identified as GIF, Hugo uses gif.DecodeAll to read all frames (to preserve animation), wrapping any decode failure in this error. It means the file's bytes are not a valid GIF stream even though the pipeline classified it as GIF, or the stream is truncated/corrupt mid-frame.","triggerScenarios":"Processing a .gif resource (`.Resize`, `.Process`, width/height access on the decoded image path) whose contents fail gif.DecodeAll — corrupt frames, truncation, or a non-GIF file with a .gif extension.","commonSituations":"Partially downloaded or corrupted GIFs in assets/; other formats (webp, mp4 'gifs' from social platforms) saved with a .gif extension; Git LFS pointer stubs in place of the binary.","solutions":["Open the file locally or run `file <path>` to confirm it's a real GIF; replace or re-download if corrupt.","If it's actually another format (often webp or mp4), rename it with the correct extension or convert it to a true GIF with `ffmpeg`/ImageMagick.","Run `git lfs pull` if LFS-tracked binaries are missing in the build environment."],"exampleFix":"// before: 'animation.gif' is actually a WebP\n$ file assets/animation.gif  # RIFF ... Web/P\n// after\n$ mv assets/animation.gif assets/animation.webp  # and update template refs","handlingStrategy":"try-catch","validationCode":"f, err := os.Open(path)\nif err != nil { return err }\ndefer f.Close()\nif _, err := gif.DecodeAll(f); err != nil {\n    return fmt.Errorf(\"corrupt gif %q: %w\", path, err)\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"failed to decode gif\") {\n    // corrupt or non-gif file with .gif extension — quarantine the asset and report it\n}","preventionTips":["Verify GIF magic bytes (GIF87a/GIF89a) for files with .gif extensions","Validate third-party/user-supplied GIFs at ingestion, not at build time","Keep original source assets so corrupt files can be re-exported"],"tags":["hugo","gif","decode","images","assets"],"analyzedSha":"8a468df065a75c1c7cf9f6850f32148746590ea5","analyzedAt":"2026-07-31T21:23:07.045Z","schemaVersion":2}