{"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":134,"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":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/engine/ccr/store.go#L116-L152","documentation":"prepareObject verifies that a typed Object's ContentHash equals \"sha256:\" + hex(sha256(Data)) when provided. A mismatch means the declared hash does not describe the actual bytes, so the store refuses the object to protect integrity; pass ContentHash empty and the store computes it for you.","triggerScenarios":"PutObject with obj.ContentHash pre-set from different data than obj.Data: data mutated after hashing, hash computed over a compressed variant while Data holds raw bytes, or hash copied from a previous object version.","commonSituations":"Caching hashes computed before a later mutation of Data; hash copied from an upstream payload that was transformed (re-encoded, trimmed) before storage; wrong hash format (missing \"sha256:\" prefix or non-hex digest).","solutions":["Leave obj.ContentHash empty and let prepareObject compute it from Data.","If supplying a hash, recompute it over the exact bytes in obj.Data right before the call.","Ensure the format is \"sha256:\" + lowercase hex digest of the data.","Check for code paths that mutate Data after hashing and reorder them."],"exampleFix":"// before\nobj.Data = transform(obj.Data)\nobj.ContentHash = oldHash // stale\n\n// after\nobj.Data = transform(obj.Data)\nsum := sha256.Sum256(obj.Data)\nobj.ContentHash = \"sha256:\" + hex.EncodeToString(sum[:])","handlingStrategy":"validation","validationCode":"func hashData(data []byte) string {\n    s := sha256.Sum256(data)\n    return \"sha256:\" + hex.EncodeToString(s[:])\n}\n// call site: if obj.ContentHash != \"\" && obj.ContentHash != hashData(obj.Data) { reject }","typeGuard":null,"tryCatchPattern":"if strings.Contains(err.Error(), \"content_hash does not match\") {\n    obj.ContentHash = \"\" // let store compute\n    return store.PutObject(obj)\n}","preventionTips":["Prefer leaving ContentHash empty and letting the store compute it.","Freeze Data before hashing; never mutate after.","Hash the exact bytes stored, not a pre-transform variant.","Keep a helper for the \"sha256:\" + hex format to avoid drift."],"tags":["ccr","integrity","checksum","typed-object"],"backgroundTag":"checksum-mismatch","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}