JuliusBrussee/caveman · error
ccr: typed object id collision
Error message
ccr: typed object id collision
What it means
In-memory/wasm CCR store: PutObject checks the in-memory map by derived ID; if the ID already exists and sameImmutableObject reports the stored clone differs from the incoming object, the put is refused as a typed-object ID collision. Same semantics as the SQLite store, just against the map. It indicates two different objects claiming one content-addressed identity.
Source
Thrown at engine/ccr/store_wasm.go:124
return append([]byte(nil), r.metadata...), nil
}
func cloneObject(obj Object) Object {
obj.Dependencies = append([]string(nil), obj.Dependencies...)
obj.Data = bytes.Clone(obj.Data)
return obj
}
func (s *Store) PutObject(input Object) (string, error) {
obj, err := prepareObject(input)
if err != nil {
return "", err
}
s.mu.Lock()
defer s.mu.Unlock()
if existing, exists := s.objects[obj.ID]; exists {
if !sameImmutableObject(existing, obj) {
return "", errors.New("ccr: typed object id collision")
}
} else {
s.objects[obj.ID] = cloneObject(obj)
}
return obj.ID, nil
}
func (s *Store) GetObject(id string) (Object, error) {
s.mu.Lock()
defer s.mu.Unlock()
obj, ok := s.objects[id]
if !ok {
return Object{}, ErrNotFound
}
return cloneObject(obj), nil
}
func (s *Store) SetObjectCurrentness(id string, currentness Currentness) error {View on GitHub (pinned to 27d5a3981a)
Solutions
- Make immutable fields deterministic: derive them from the same inputs every time, never mutate them between puts
- Change the Data (and thus ContentHash and ID) when the object's content genuinely changes
- Diff existing := store.GetObject(obj.ID) against the incoming object to locate the divergent field
Example fix
// before
objA := ccr.Object{Type: t, SessionID: s, Source: src, Data: d, StoredByteLength: 10}
objB := ccr.Object{Type: t, SessionID: s, Source: src, Data: d, StoredByteLength: 20} // same identity, different length
store.PutObject(objA); store.PutObject(objB) // collision
// after
objB.StoredByteLength = 0 // let the store derive it; identical inputs -> identical immutable object Defensive patterns
Strategy: validation
Validate before calling
if prev, ok := s.objectsByID[obj.ID]; ok && !sameImmutable(prev, obj) {
obj.ContentHash = ""
} Try / catch
if _, err := store.PutObject(obj); err != nil {
if strings.Contains(err.Error(), "id collision") {
// reconcile: rebuild object from identical inputs
}
} Prevention
- Never mutate shared Object structs between puts (clone first)
- Let the store derive lengths and hash where possible
- Keep object construction in one code path per type
When it happens
Trigger: Two PutObject calls with identical (Type, SessionID, Source, RepositoryState, ContentHash) but differing immutable metadata (e.g. Dependencies or byte lengths); re-putting after changing fields the store treats as immutable without changing the content hash.
Common situations: Browser/wasm client re-submitting an object whose metadata was locally augmented between attempts; test code mutating shared object structs between puts.
Related errors
- ccr: typed object id collision
- ccr: recovery handle not found
- ccr: storage budget exceeded
- ccr: typed object session_id is required
- ccr: typed object content_hash does not match data
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/2ca4108d849418ec.
Report an issue: GitHub.