From 1e6c5b57b2c1061f024fc068db0b29a722298075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Tue, 17 Feb 2026 13:46:05 +0100 Subject: [PATCH] fix: use pickMorphGroupSurvivor in relation loader to match field metadata deduplication (#17988) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Fixes "Target field metadata full object not found" error thrown during optimistic effects (e.g., bulk delete) on workspaces with custom objects - The relation loader was using a simple sort-by-ID to pick the representative morph field, while `filterMorphRelationDuplicateFields` uses `pickMorphGroupSurvivor` which prefers active non-system fields. When a custom object's auto-created morph field (`isSystem: true`) happened to have the smallest UUID, the two loaders would disagree — the relation DTO pointed to that system field's ID, but the field metadata loader filtered it out in favor of a standard field, causing the frontend lookup to fail. - Now both code paths use `pickMorphGroupSurvivor` so they always agree on which morph field represents the group. ## Test plan - [ ] Create a custom object on a workspace that already has standard objects with morph relations (e.g., noteTarget, taskTarget) - [ ] Bulk-select and delete records (e.g., People) — should no longer throw "Target field metadata full object not found" Made with [Cursor](https://cursor.com) --- > [!NOTE] > **Low Risk** > Small, localized change to morph-relation selection logic in a dataloader; main risk is altered field choice for edge-case morph groups, but behavior now matches existing deduplication. > > **Overview** > Ensures the relation dataloader picks the representative morph-relation target field using `pickMorphGroupSurvivor` (preferring active non-system fields) instead of the previous sort-by-id approach. > > This aligns `createRelationLoader` with `filterMorphRelationDuplicateFields`, preventing mismatches where relation DTOs could reference a morph field that gets filtered out elsewhere (e.g., triggering “Target field metadata full object not found”). > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit c3a6d861262cd21c49cb7582767d419e8048980d. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). Co-authored-by: Cursor --- .../src/engine/dataloaders/dataloader.service.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts b/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts index 695a0df26e..2d06323fb5 100644 --- a/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts +++ b/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts @@ -10,6 +10,7 @@ import { type IndexMetadataInterface } from 'src/engine/metadata-modules/index-m import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { type IDataloaders } from 'src/engine/dataloaders/dataloader.interface'; import { filterMorphRelationDuplicateFields } from 'src/engine/dataloaders/utils/filter-morph-relation-duplicate-fields.util'; +import { pickMorphGroupSurvivor } from 'src/engine/dataloaders/utils/pick-morph-group-survivor.util'; import { FIELD_METADATA_STANDARD_OVERRIDES_PROPERTIES } from 'src/engine/metadata-modules/field-metadata/constants/field-metadata-standard-overrides-properties.constant'; import { type FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; import { RelationDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation.dto'; @@ -188,14 +189,18 @@ export class DataloaderService { flatFieldMetadataMaps, flatObjectMetadata: targetFlatObjectMetadata, }), - ].sort((a, b) => (a.id > b.id ? 1 : -1)); + ]; + + const survivorMorphField = pickMorphGroupSurvivor( + allMorphFlatFieldMetadatas, + ); relationDtos.push( fromMorphOrRelationFlatFieldMetadataToRelationDto({ sourceFlatFieldMetadata, sourceFlatObjectMetadata, targetFlatFieldMetadata: { - ...allMorphFlatFieldMetadatas[0], + ...survivorMorphField, name: morphNameFromMorphFieldMetadataName, }, targetFlatObjectMetadata,