JuliusBrussee/caveman · error

ccr: typed object content_hash does not match data

Error message

ccr: typed object content_hash does not match data

What it means

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.

Solutions

  1. Leave obj.ContentHash empty and let prepareObject compute it from Data.
  2. If supplying a hash, recompute it over the exact bytes in obj.Data right before the call.
  3. Ensure the format is "sha256:" + lowercase hex digest of the data.
  4. Check for code paths that mutate Data after hashing and reorder them.

Example fix

// before
obj.Data = transform(obj.Data)
obj.ContentHash = oldHash // stale

// after
obj.Data = transform(obj.Data)
sum := sha256.Sum256(obj.Data)
obj.ContentHash = "sha256:" + hex.EncodeToString(sum[:])
Defensive patterns

Strategy: validation

Validate before calling

func hashData(data []byte) string {
    s := sha256.Sum256(data)
    return "sha256:" + hex.EncodeToString(s[:])
}
// call site: if obj.ContentHash != "" && obj.ContentHash != hashData(obj.Data) { reject }

Try / catch

if strings.Contains(err.Error(), "content_hash does not match") {
    obj.ContentHash = "" // let store compute
    return store.PutObject(obj)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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).

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/e926bea2c6cc7943. Report an issue: GitHub.

Appendix: source

Thrown at engine/ccr/store.go:134

		return Object{}, fmt.Errorf("ccr: unknown currentness %q", obj.Currentness)
	}
	if obj.Lifecycle == "" {
		obj.Lifecycle = Hot
	}
	if obj.Lifecycle != Hot && obj.Lifecycle != Warm && obj.Lifecycle != Cold && obj.Lifecycle != LifecycleArchived {
		return Object{}, fmt.Errorf("ccr: unknown lifecycle %q", obj.Lifecycle)
	}
	if obj.CreatedAt.IsZero() {
		obj.CreatedAt = time.Now().UTC()
	} else {
		obj.CreatedAt = obj.CreatedAt.UTC()
	}
	dataSum := sha256.Sum256(obj.Data)
	computedHash := "sha256:" + hex.EncodeToString(dataSum[:])
	if obj.ContentHash == "" {
		obj.ContentHash = computedHash
	} else if obj.ContentHash != computedHash {
		return Object{}, errors.New("ccr: typed object content_hash does not match data")
	}
	if obj.OriginalByteLength == 0 {
		obj.OriginalByteLength = len(obj.Data)
	}
	if obj.StoredByteLength == 0 {
		obj.StoredByteLength = len(obj.Data)
	}
	if obj.OriginalByteLength < 0 || obj.StoredByteLength < 0 {
		return Object{}, errors.New("ccr: typed object byte lengths cannot be negative")
	}
	if obj.ID == "" {
		identity := strings.Join([]string{string(obj.Type), obj.SessionID, obj.Source, obj.RepositoryState, obj.ContentHash}, "\x00")
		sum := sha256.Sum256([]byte(identity))
		obj.ID = "ccr_obj_" + hex.EncodeToString(sum[:16])
	}
	obj.Dependencies = append([]string(nil), obj.Dependencies...)
	obj.Data = bytes.Clone(obj.Data)
	return obj, nil

View on GitHub (pinned to 3ee70a1026)