{"record":{"id":"30d7c01b17cd6ae7","repo":"amir20/dozzle","slug":"invalid-image-reference-q-empty-digest","errorCode":null,"errorMessage":"invalid image reference %q: empty digest","messagePattern":"invalid image reference %q: empty digest","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/imagecheck/reference.go","lineNumber":101,"sourceCode":"\treturn fmt.Sprintf(\"%s://%s/v2/%s/manifests/%s\", r.scheme(), r.host(), r.Repository, target)\n}\n\n// ParseReference splits an image reference into its registry, repository and\n// tag/digest parts, applying Docker's implicit defaults.\nfunc ParseReference(ref string) (Reference, error) {\n\tif ref == \"\" {\n\t\treturn Reference{}, fmt.Errorf(\"empty image reference\")\n\t}\n\n\tremainder := ref\n\tvar digest string\n\t// A digest always trails the reference and may follow a tag, as in\n\t// \"nginx:1.25@sha256:abc...\".\n\tif i := strings.Index(remainder, \"@\"); i != -1 {\n\t\tdigest = remainder[i+1:]\n\t\tremainder = remainder[:i]\n\t\tif digest == \"\" {\n\t\t\treturn Reference{}, fmt.Errorf(\"invalid image reference %q: empty digest\", ref)\n\t\t}\n\t}\n\n\tregistry := defaultRegistry\n\t// The first path component is a registry only when it looks like a host:\n\t// it contains a dot or port separator, or is localhost. Otherwise it is a\n\t// Docker Hub namespace such as \"amir20\" in \"amir20/dozzle\".\n\tif i := strings.Index(remainder, \"/\"); i != -1 {\n\t\tcandidate := remainder[:i]\n\t\tif candidate == \"localhost\" || strings.ContainsAny(candidate, \".:\") {\n\t\t\tregistry = candidate\n\t\t\tremainder = remainder[i+1:]\n\t\t}\n\t}\n\n\t// Docker Hub answers to several names. They have to collapse to one, or a\n\t// reference written as index.docker.io/library/nginx never lines up with\n\t// the \"nginx@sha256:...\" that Docker records locally.","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/amir20/dozzle/blob/d9463cbe21874e44ab79db6fa63e746ca7d22928/internal/imagecheck/reference.go#L83-L119","documentation":"When the reference contains '@', everything after it is the digest; if it is empty (reference ends with '@'), ParseReference returns this error because a digest-qualified reference must carry a digest value.","triggerScenarios":"Passing references like \"nginx@\", \"nginx:1.25@\", or \"repo:5000/img@\" where the '@' delimiter exists but nothing follows it.","commonSituations":"String concatenation that appends '@' plus a digest fetched asynchronously but failed, templates that render '@{{digest}}' as bare '@', copy-paste truncation of a digest-pinned image name.","solutions":["Remove the trailing '@' if you did not intend digest pinning","Provide the full digest, e.g. nginx:1.25@sha256:<64 hex chars>","Guard the string building code so the digest is only appended when non-empty"],"exampleFix":"// before\nref := \"nginx:1.25@\" + digest // digest may be \"\"\n// after\nvar ref string\nif digest != \"\" {\n    ref = \"nginx:1.25@\" + digest\n} else {\n    ref = \"nginx:1.25\"\n}","handlingStrategy":"validation","validationCode":"if strings.HasSuffix(ref, \"@\") {\n    return fmt.Errorf(\"reference %q ends with '@' but has no digest\", ref)\n}","typeGuard":"func isDigestPinned(ref string) bool {\n    i := strings.Index(ref, \"@\")\n    return i != -1 && i < len(ref)-1\n}","tryCatchPattern":"if _, err := imagecheck.ParseReference(ref); err != nil {\n    if strings.Contains(err.Error(), \"empty digest\") {\n        ref = strings.TrimSuffix(ref, \"@\")\n    }\n}","preventionTips":["Only append '@' + digest when the digest is non-empty","Prefer tag-pinned references unless you truly digest-pin","Validate image strings at config load time"],"tags":["image","reference-parsing","digest","validation"],"backgroundTag":"invalid-argument-format","analyzedSha":"d9463cbe21874e44ab79db6fa63e746ca7d22928","analyzedAt":"2026-09-07T10:08:55.855Z","contentChangedAt":"2026-09-07T10:08:55.855Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}