{"record":{"id":"e926bea2c6cc7943","repo":"JuliusBrussee/caveman","slug":"ccr-typed-object-content-hash-does-not-match-data","errorCode":null,"errorMessage":"ccr: typed object content_hash does not match data","messagePattern":"ccr: typed object content_hash does not match data","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/ccr/store.go","lineNumber":130,"sourceCode":"\t\treturn Object{}, fmt.Errorf(\"ccr: unknown currentness %q\", obj.Currentness)\n\t}\n\tif obj.Lifecycle == \"\" {\n\t\tobj.Lifecycle = Hot\n\t}\n\tif obj.Lifecycle != Hot && obj.Lifecycle != Warm && obj.Lifecycle != Cold && obj.Lifecycle != LifecycleArchived {\n\t\treturn Object{}, fmt.Errorf(\"ccr: unknown lifecycle %q\", obj.Lifecycle)\n\t}\n\tif obj.CreatedAt.IsZero() {\n\t\tobj.CreatedAt = time.Now().UTC()\n\t} else {\n\t\tobj.CreatedAt = obj.CreatedAt.UTC()\n\t}\n\tdataSum := sha256.Sum256(obj.Data)\n\tcomputedHash := \"sha256:\" + hex.EncodeToString(dataSum[:])\n\tif obj.ContentHash == \"\" {\n\t\tobj.ContentHash = computedHash\n\t} else if obj.ContentHash != computedHash {\n\t\treturn Object{}, errors.New(\"ccr: typed object content_hash does not match data\")\n\t}\n\tif obj.OriginalByteLength == 0 {\n\t\tobj.OriginalByteLength = len(obj.Data)\n\t}\n\tif obj.StoredByteLength == 0 {\n\t\tobj.StoredByteLength = len(obj.Data)\n\t}\n\tif obj.OriginalByteLength < 0 || obj.StoredByteLength < 0 {\n\t\treturn Object{}, errors.New(\"ccr: typed object byte lengths cannot be negative\")\n\t}\n\tif obj.ID == \"\" {\n\t\tidentity := strings.Join([]string{string(obj.Type), obj.SessionID, obj.Source, obj.RepositoryState, obj.ContentHash}, \"\\x00\")\n\t\tsum := sha256.Sum256([]byte(identity))\n\t\tobj.ID = \"ccr_obj_\" + hex.EncodeToString(sum[:16])\n\t}\n\tobj.Dependencies = append([]string(nil), obj.Dependencies...)\n\tobj.Data = bytes.Clone(obj.Data)\n\treturn obj, nil","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/engine/ccr/store.go#L112-L148","documentation":"prepareObject computes sha256 over obj.Data and compares it to the caller-supplied obj.ContentHash. If you set ContentHash yourself it must be exactly `sha256:` + lowercase hex of the SHA-256 of Data; any mismatch is rejected because content-addressed identity and dedup depend on the hash being truthful.","triggerScenarios":"Passing a ContentHash computed with a different algorithm or encoding (no `sha256:` prefix, uppercase hex, base64); computing the hash over the original data but then mutating Data (compression, trimming) before PutObject; copying ContentHash from a different object.","commonSituations":"Data transformed (reflow/compression) between hashing and storing; hash generated by another language/tool with different hex casing; stale hash left on a struct whose Data field is later overwritten.","solutions":["Omit ContentHash entirely — prepareObject fills it in for you","Or compute it as `\"sha256:\" + hex.EncodeToString(sha256.Sum256(data)[:])` over the exact Data bytes you pass","Re-derive the hash after any transformation of Data, immediately before PutObject"],"exampleFix":"// before\nsum := sha256.Sum256(original)\nobj := ccr.Object{ContentHash: hex.EncodeToString(sum[:]), Data: transformed} // wrong: missing prefix, hashed wrong bytes\n\n// after\nobj := ccr.Object{Data: transformed} // ContentHash auto-computed\n// or: obj.ContentHash = \"sha256:\" + hex.EncodeToString(sha256.Sum256(transformed)[:])","handlingStrategy":"validation","validationCode":"if obj.ContentHash != \"\" {\n    sum := sha256.Sum256(obj.Data)\n    if obj.ContentHash != \"sha256:\"+hex.EncodeToString(sum[:]) {\n        return errors.New(\"content_hash stale; clear it or recompute\")\n    }\n}\nstore.PutObject(obj)","typeGuard":"func contentHashMatches(data []byte, h string) bool {\n    sum := sha256.Sum256(data)\n    return h == \"sha256:\"+hex.EncodeToString(sum[:])\n}","tryCatchPattern":null,"preventionTips":["Leave ContentHash empty and let the store compute it","Never mutate Data after setting ContentHash","Hash the exact bytes you pass, with the sha256: hex format"],"tags":["ccr","go","integrity","sha256","content-hash"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}