From b3b0c27f669925eedd3f4b30da44daa8fa86c296 Mon Sep 17 00:00:00 2001 From: Sonarly Claude Code Date: Wed, 4 Mar 2026 03:52:18 +0000 Subject: [PATCH] ViewField delete fails: runner loads stale cache after DB cascade deletes viewFields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://sonarly.com/issue/3344?type=bug When deactivating a field, the migration runner independently loads flat entity maps from cache to resolve viewField universal identifiers, but a concurrent operation (or the same migration's earlier view delete with DB CASCADE) can remove the viewField from the underlying data between the builder and runner cache reads, causing a lookup failure. Fix: ## Root Cause The orchestrator was ordering view-related delete actions with parent `view.delete` **before** child `viewField.delete` (and other child entity deletes). Because `ViewFieldEntity.view` has `onDelete: 'CASCADE'`, when the runner executes `view.delete` it issues a hard `DELETE` against the `view` table; the database CASCADE automatically hard-deletes all child `viewField` rows. However, the runner's optimistic cache update only removes the view from `flatViewMaps` — it does **not** cascade-remove the viewFields from `flatViewFieldMaps`. This leaves a stale/inconsistent optimistic cache. Then when the runner processes the subsequent `viewField.delete` action, `transpileUniversalDeleteActionToFlatDeleteAction` tries to look up the viewField by its universal identifier in the (now stale) `allFlatEntityMaps`. Since the DB already deleted it via CASCADE, any concurrent operation or cache invalidation that triggers a fresh load will produce a snapshot missing the viewField, causing `findFlatEntityByUniversalIdentifierOrThrow` to throw — wrapped as `WorkspaceMigrationRunnerException` with message `"Migration action 'delete' for 'viewField' failed"`. ## Fix Reorder the view-related actions in `workspace-migration-build-orchestrator.service.ts` so that all child-entity deletes (`viewFilter`, `viewFilterGroup`, `viewGroup`, `viewFieldGroup`, `viewField`) are executed **before** `view.delete`. This ensures: 1. Child entities are explicitly deleted and removed from the optimistic cache first. 2. When `view.delete` runs and the DB CASCADE fires, there are no remaining child rows in either the DB or the optimistic cache — both are already consistent. 3. The subsequent (now empty) child-delete lists are no-ops, maintaining correctness. ```typescript file=packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service.ts lines=747-768 // Views // Child entities must be deleted before parent views to prevent // DB CASCADE from removing them before the runner's optimistic // cache processes those delete actions (causing lookup failures). ...aggregatedOrchestratorActionsReport.viewFilter.delete, ...aggregatedOrchestratorActionsReport.viewFilterGroup.delete, ...aggregatedOrchestratorActionsReport.viewGroup.delete, ...aggregatedOrchestratorActionsReport.viewFieldGroup.delete, ...aggregatedOrchestratorActionsReport.viewField.delete, ...aggregatedOrchestratorActionsReport.view.delete, ...aggregatedOrchestratorActionsReport.view.create, ...aggregatedOrchestratorActionsReport.view.update, ...aggregatedOrchestratorActionsReport.viewFieldGroup.create, ...aggregatedOrchestratorActionsReport.viewFieldGroup.update, ...aggregatedOrchestratorActionsReport.viewField.create, ...aggregatedOrchestratorActionsReport.viewField.update, ...aggregatedOrchestratorActionsReport.viewFilterGroup.create, ...aggregatedOrchestratorActionsReport.viewFilterGroup.update, ...aggregatedOrchestratorActionsReport.viewFilter.create, ...aggregatedOrchestratorActionsReport.viewFilter.update, ...aggregatedOrchestratorActionsReport.viewGroup.create, ...aggregatedOrchestratorActionsReport.viewGroup.update, ``` Note: This fix eliminates the intra-migration ordering problem. The separate TOCTOU race between concurrent migrations (independent builder and runner cache reads) remains a narrower architectural gap, but the deterministic failure path described in the Sentry error is resolved by this ordering fix. --- ...orkspace-migration-build-orchestrator.service.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service.ts index b82543b2932..04382800030 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service.ts @@ -745,22 +745,25 @@ export class WorkspaceMigrationBuildOrchestratorService { /// // Views + // Child entities must be deleted before parent views to prevent + // DB CASCADE from removing them before the runner's optimistic + // cache processes those delete actions (causing lookup failures). + ...aggregatedOrchestratorActionsReport.viewFilter.delete, + ...aggregatedOrchestratorActionsReport.viewFilterGroup.delete, + ...aggregatedOrchestratorActionsReport.viewGroup.delete, + ...aggregatedOrchestratorActionsReport.viewFieldGroup.delete, + ...aggregatedOrchestratorActionsReport.viewField.delete, ...aggregatedOrchestratorActionsReport.view.delete, ...aggregatedOrchestratorActionsReport.view.create, ...aggregatedOrchestratorActionsReport.view.update, - ...aggregatedOrchestratorActionsReport.viewField.delete, - ...aggregatedOrchestratorActionsReport.viewFieldGroup.delete, ...aggregatedOrchestratorActionsReport.viewFieldGroup.create, ...aggregatedOrchestratorActionsReport.viewFieldGroup.update, ...aggregatedOrchestratorActionsReport.viewField.create, ...aggregatedOrchestratorActionsReport.viewField.update, - ...aggregatedOrchestratorActionsReport.viewFilterGroup.delete, ...aggregatedOrchestratorActionsReport.viewFilterGroup.create, ...aggregatedOrchestratorActionsReport.viewFilterGroup.update, - ...aggregatedOrchestratorActionsReport.viewFilter.delete, ...aggregatedOrchestratorActionsReport.viewFilter.create, ...aggregatedOrchestratorActionsReport.viewFilter.update, - ...aggregatedOrchestratorActionsReport.viewGroup.delete, ...aggregatedOrchestratorActionsReport.viewGroup.create, ...aggregatedOrchestratorActionsReport.viewGroup.update, ///