It seems it was a problem with multiple save requests coming in concurrently (from different managed object contexts, but different threads nonetheless). The solution was to wrap the save operations in a @synchronized block to ensure that the save operations were atomic and did not happen concurrently:
- (void) save {
// lock and wait if another save operation is in progress
@synchronized([self class]) {
NSError *error;
if (![self save:&error]) {
NSLog(@"Whoops, couldn't save: %@", error);
}
}
}