New request
#20841: Upgrade v2.4.0 → v2.7.3 fails on clean DB
We'll provision a sandbox, run an agent against the issue, and open a draft PR. You can pull the branch and iterate from there.
Two distinct upgrade failures: v2.4.0 → v2.6.2 (column relationTargetFieldMetadataId does not exist) AND v2.4.0 → v2.7.3 (universalIdentifier undefined in WorkspaceRolesPermissionsCacheService)
Summary
Attempted self-hosted upgrade from a clean v2.4.0 instance to the latest available image. Hit two distinct failures, both in workspace cache recomputation that runs at the start of the upgrade sequence — before the actual schema migrations apply:
-
v2.4.0 → v2.6.2:
WorkspaceFlatViewFilterMapCacheService.computeForCacheSELECTsrelationTargetFieldMetadataIdcolumn that the corresponding schema migration is supposed to add later in the same sequence. The column doesn't exist yet → upgrade aborts at step 72 (2.5.0_NormalizeCompositeFieldDefaults). -
v2.4.0 → v2.7.3 (skipping 2.6, from clean snapshot):
WorkspaceRolesPermissionsCacheService.hasSettingsGatedObjectPermissionscalls.find()on permission flags, getsundefined, then reads.universalIdentifieron it → TypeError. Same step (72), different code path.
Both failures happen before any DB writes, so DB stays clean (verified by Elestio snapshot restore between attempts).
The same workspace runs perfectly on v2.4.0. Pre-upgrade upgrade:status reports Up to date / 0 failures. Following the official upgrade procedure from the docs (and confirmed-safe by @prastoin in Discord).
Environment
- Initial server:
twentycrm/twenty:v2.4.0(Docker, official) - Target image (attempt 1):
twentycrm/twenty:v2.6.2 - Target image (attempt 2):
twentycrm/twenty:v2.7.3 - Twenty SDK (custom app):
twenty-sdk@2.4.0+twenty-client-sdk@2.4.0 - Postgres:
elestio/postgres:16 - Deployment: Elestio managed Docker Compose, single workspace
- OS: Ubuntu 7.0.0-15-generic
ENABLE_DB_MIGRATIONS:"true"on server,"false"on worker
Workspace shape
- 1 workspace (
bf6fe561-902d-43cf-a7f7-e65c69a895c4) - 5 roles: Admin (system), Operátor (custom, created via API), Technik (custom, created via API), Member (default),
My Twenty default function role(created by SDKdefineRolefor app logic-functions) - ~5 custom objects, ~40 custom fields
- Multiple
defineLogicFunctions (event-driven + cron),defineFrontComponentwidgets,definePageLayoutwithFRONT_COMPONENTwidgets - Several saved views with view filters (uses
viewFiltertable)
Pre-flight (always clean on v2.4.0)
docker exec twenty-server yarn command:prod upgrade:status
APP_VERSION: v2.4.0
Instance
Inferred version: 2.4.0
Latest command: AddApplicationIdToPublicDomain 1798000003000 (2.4.0) (instance fast)
Status: Up to date
Executed by: v2.4.0
At: 2026-05-15T18:55:28.683Z
Workspace
Servis HVAC (bf6fe561-902d-43cf-a7f7-e65c69a895c4)
Inferred version: 2.4.0
Latest command: MigrateToBillingV2 1797000001000 (2.4.0) (workspace)
Status: Up to date
Executed by: v2.4.0
At: 2026-05-15T18:55:28.694Z
Summary
Instance: Up to date
Workspaces: 1 up to date, 0 behind, 0 failed (1 total)
Failure #1: v2.4.0 → v2.6.2
Reproduction
- Verify clean v2.4.0 state (as above).
- Stop stack, bump image tag to
twentycrm/twenty:v2.6.2in compose,docker compose up -d. - Server boots; auto-runs upgrade sequence on startup (default behavior with
ENABLE_DB_MIGRATIONS=true).
Expected
Server applies all migrations 2.4 → 2.6, workspace cache recomputes, app available.
Actual
Upgrade sequence enters workspace step 72 (2.5.0_NormalizeCompositeFieldDefaults), but workspace cache recomputation triggers a SELECT against viewFilter that includes the relationTargetFieldMetadataId column — which is added by step 76 (2.6.0_AddRelationTargetFieldMetadataIdToViewFilter). Workspace fails immediately, sequence aborts.
[Nest] LOG [UpgradeCommand] Initialized upgrade sequence: 77 step(s)
[upgrade] event=sequence.initialized stepCount=77 dryRun=false
[Nest] LOG [WorkspaceIteratorService] Running on workspace bf6fe561-902d-43cf-a7f7-e65c69a895c4 1/1
[Nest] LOG [WorkspaceCommandRunnerService] Upgrading workspace bf6fe561-902d-43cf-a7f7-e65c69a895c4 1/1
[upgrade] event=workspace.start workspaceId=bf6fe561-902d-43cf-a7f7-e65c69a895c4 index=1 total=1 dryRun=false
query failed: SELECT
"ViewFilterEntity"."workspaceId",
"ViewFilterEntity"."universalIdentifier",
"ViewFilterEntity"."applicationId",
"ViewFilterEntity"."id",
"ViewFilterEntity"."fieldMetadataId",
"ViewFilterEntity"."operand",
"ViewFilterEntity"."value",
"ViewFilterEntity"."viewFilterGroupId",
"ViewFilterEntity"."positionInViewFilterGroup",
"ViewFilterEntity"."subFieldName",
"ViewFilterEntity"."relationTargetFieldMetadataId", -- ⚠️ column added by step 76, sequence is at step 72
"ViewFilterEntity"."viewId",
"ViewFilterEntity"."createdAt",
"ViewFilterEntity"."updatedAt",
"ViewFilterEntity"."deletedAt"
FROM "core"."viewFilter" "ViewFilterEntity"
WHERE "ViewFilterEntity"."workspaceId" = $1
error: column ViewFilterEntity.relationTargetFieldMetadataId does not exist
[Nest] ERROR [WorkspaceIteratorService] Error in workspace bf6fe561-902d-43cf-a7f7-e65c69a895c4:
column ViewFilterEntity.relationTargetFieldMetadataId does not exist
QueryFailedError: column ViewFilterEntity.relationTargetFieldMetadataId does not exist
at PostgresQueryRunner.query (/app/node_modules/typeorm/driver/postgres/PostgresQueryRunner.js:219:19)
at SelectQueryBuilder.loadRawResults (/app/node_modules/typeorm/query-builder/SelectQueryBuilder.js:2192:25)
at SelectQueryBuilder.executeEntitiesAndRawResults (/app/node_modules/typeorm/query-builder/SelectQueryBuilder.js:2040:26)
at SelectQueryBuilder.getRawAndEntities (/app/node_modules/typeorm/query-builder/SelectQueryBuilder.js:684:29)
at SelectQueryBuilder.getMany (/app/node_modules/typeorm/query-builder/SelectQueryBuilder.js:750:25)
at Promise.all (index 0)
at WorkspaceFlatViewFilterMapCacheService.computeForCache (/app/packages/twenty-server/dist/engine/metadata-modules/flat-view-filter/services/workspace-flat-view-filter-map-cache.service.js:41:86)
at /app/packages/twenty-server/dist/engine/workspace-cache/services/workspace-cache.service.js:220:26
at Promise.all (index 3)
[Nest] ERROR [UpgradeSequenceRunnerService] Workspace steps ended with 1 failure(s). Aborting — cannot proceed to next instance step.
[upgrade] event=sequence.aborted failures=1 reason=workspace-failures
[Nest] LOG [UpgradeCommand] Upgrade summary: 0 workspace(s) succeeded, 1 workspace(s) failed
[upgrade] event=summary totalSuccesses=0 totalFailures=1 dryRun=false
[Nest] ERROR [UpgradeCommand] Upgrade failed: Upgrade completed with 1 workspace failure(s)
[upgrade] event=aborted
Post-failure state
After failure, upgrade:status shows DB in mid-state:
APP_VERSION: v2.6.2
Instance
Inferred version: 2.5.0
Latest command: EncryptTotpSecrets 1798000009000 (2.5.0) (instance slow)
Status: Behind
Executed by: v2.6.2
Workspace
Failed at: NormalizeCompositeFieldDefaults 1778000001000 (2.5.0) (workspace)
Servis HVAC: Status: Failed, Executed by: v2.6.2, Error: column ViewFilterEntity.relationTargetFieldMetadataId does not exist
DB now has 2.5.0 instance-level changes applied but workspace upgrade is Failed. Rolling back image tag to v2.4.0 results in HTTP 502 — old server can't read mid-state DB. The failed marker also blocks retry under any image (server sees failed workspace and refuses to retry without a reset CLI helper).
Recovery: Elestio snapshot restore to pre-attempt state.
Failure #2: v2.4.0 → v2.7.3 (after snapshot restore to clean v2.4.0)
Reproduction
-
Restore Elestio snapshot of clean v2.4.0 → verify
upgrade:statusshowsUp to date / 0 failures. -
Stop stack, bump image tag to
twentycrm/twenty:v2.7.3,docker compose up -d. -
Server starts (HTTP 200) but UI shows skeleton loading state —
/metadatareturns empty. -
Run dry-run to capture clean stack:
docker exec twenty-server yarn command:prod upgrade --dry-run -v
Expected
Dry-run plans all 83 sequence steps and reports planned changes without errors. Subsequent real upgrade applies them.
Actual
Sequence enumerated successfully (83 steps including the new 2.6.0_AddRelationTargetFieldMetadataIdToViewFilter fast-instance step that was missing-in-order in v2.6.2). Failure happens during the per-workspace cache recomputation at step 72 — different stack from Failure #1:
[Nest] LOG [UpgradeCommand] Initialized upgrade sequence: 83 step(s)
... (steps 1..71 enumerated) ...
[72] workspace — 2.5.0_NormalizeCompositeFieldDefaultsCommand_1778000001000 (2.5.0)
[73] fast-instance — 2.6.0_RenamePermissionFlagToRolePermissionFlagFastInstanceCommand_1778235340020 (2.6.0)
[74] fast-instance — 2.6.0_PermissionFlagSyncableEntityFastInstanceCommand_1778235340021 (2.6.0)
[75] fast-instance — 2.6.0_LinkRolePermissionFlagToPermissionFlagFastInstanceCommand_1778235340022 (2.6.0)
[76] fast-instance — 2.6.0_AddRelationTargetFieldMetadataIdToViewFilterFastInstanceCommand_1798000005000 (2.6.0)
... (steps 77..83) ...
[Nest] LOG [WorkspaceIteratorService] Dry run mode: No changes will be applied
[Nest] LOG [WorkspaceIteratorService] Running on workspace bf6fe561-902d-43cf-a7f7-e65c69a895c4 1/1
[Nest] LOG [UpgradeAwareRepositoryProxy] [upgrade-proxy] strip relation RolePermissionFlagEntity.permissionFlag -> PermissionFlagEntity
[Nest] ERROR [WorkspaceIteratorService] Error in workspace bf6fe561-902d-43cf-a7f7-e65c69a895c4:
Cannot read properties of undefined (reading 'universalIdentifier')
TypeError: Cannot read properties of undefined (reading 'universalIdentifier')
at /app/packages/twenty-server/dist/engine/metadata-modules/role/services/workspace-roles-permissions-cache.service.js:187:156
at Array.find (<anonymous>)
at WorkspaceRolesPermissionsCacheService.hasSettingsGatedObjectPermissions
(/app/packages/twenty-server/dist/engine/metadata-modules/role/services/workspace-roles-permissions-cache.service.js:187:95)
at WorkspaceRolesPermissionsCacheService.computeForCache
(/app/packages/twenty-server/dist/engine/metadata-modules/role/services/workspace-roles-permissions-cache.service.js:132:69)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async /app/packages/twenty-server/dist/engine/workspace-cache/services/workspace-cache.service.js:220:26
at async Promise.all (index 5)
at async WorkspaceCacheService.recomputeDataFromProvider (/app/packages/twenty-server/dist/engine/workspace-cache/services/workspace-cache.service.js:228:26)
at async /app/packages/twenty-server/dist/engine/workspace-cache/services/workspace-cache.service.js:92:36
at async /app/packages/twenty-server/dist/engine/twenty-orm/storage/promise-memoizer.storage.js:27:31
[Nest] ERROR [UpgradeSequenceRunnerService] Workspace steps ended with 1 failure(s). Aborting — cannot proceed to next instance step.
[upgrade] event=sequence.aborted failures=1 reason=workspace-failures
[Nest] LOG [UpgradeCommand] Upgrade summary: 0 workspace(s) succeeded, 1 workspace(s) failed
[upgrade] event=summary totalSuccesses=0 totalFailures=1 dryRun=true
[Nest] ERROR [UpgradeCommand] Upgrade failed: Upgrade completed with 1 workspace failure(s)
Note on the upgrade-proxy log line
The line [upgrade-proxy] strip relation RolePermissionFlagEntity.permissionFlag -> PermissionFlagEntity appears immediately before the error. This suggests the UpgradeAwareRepositoryProxy is stripping the permissionFlag relation from RolePermissionFlagEntity for cross-version compatibility — possibly leaving the .find() lookup with no target to match against, which then returns undefined.
Same error on real (non-dry-run) upgrade
Running command:prod upgrade (without --dry-run) produces the identical stack. Real upgrade also fails before applying any migration, so DB remains in v2.4.0 state (verified via upgrade:status after rollback).
Root cause hypotheses
#1 (v2.6 ordering bug)
The workspace cache recomputes during upgrade workspace steps before the cross-version schema migrations have applied. The cache SELECT uses the target entity schema (which references relationTargetFieldMetadataId), but the schema migration that adds the column runs after the per-workspace step in the sequence. Either:
- The workspace cache should be deferred until after all schema-adding migrations have applied, OR
- The
UpgradeAwareRepositoryProxyshould also strip columns that don't yet exist in the DB (it appears to handle relations likeRolePermissionFlagEntity.permissionFlagper failure #2's log line, but not new columns onviewFilter).
#2 (v2.7 universalIdentifier null)
hasSettingsGatedObjectPermissions calls .find() on a permission-flag collection, gets undefined, then dereferences .universalIdentifier. The [upgrade-proxy] strip relation line just above suggests the proxy stripped the relation, leaving the .find() with nothing to match. Possibilities:
- One of our 5 roles (likely the SDK-created
defineRole"default function role") has aRolePermissionFlagrow pointing at aPermissionFlag.universalIdentifierthat doesn't exist in the v2.7 catalog (renamed/removed in v2.6RenamePermissionFlagToRolePermissionFlagmigration). permissionFlagrows haveuniversalIdentifier=nulland the proxy doesn't normalize them.
Either way, the cache compute should probably be defensive (skip permission flag entries that can't be resolved instead of throwing), or the upgrade sequence should run Backfill migrations before recomputing cache.
What I've tried
- ✅ Verified clean DB state before each attempt (
upgrade:status: Up to date / 0 failureson v2.4.0). - ✅ Verified Elestio snapshot restore produces clean DB (no residual failed markers from v2.6.2 attempt).
- ✅ Attempt #2 also tested with
--dry-run -v— same error, same stack, no DB writes. - ✅ Verified workspace runs perfectly on v2.4.0 after rollback (HTTP 200, full UI, all 18 records visible in test view).
- ❌ Did not try deleting custom roles before upgrade (would lose Operátor/Technik role config; reproducible via setup scripts but disruptive).
- ❌ Did not try manually adding
relationTargetFieldMetadataIdcolumn before upgrade (didn't want to corrupt schema further).
Questions for maintainers
- Failure #1: Is the v2.6 ordering bug already tracked? Is the fix to defer workspace cache recomputation until after schema migrations, or to extend
UpgradeAwareRepositoryProxyto strip unknown columns? - Failure #2: Is there a known issue with
defineRoleSDK roles + v2.6RenamePermissionFlagToRolePermissionFlagmigration? Should SDK-created roles be deleted/recreated, or is there a backfill we're missing? - Is there a CLI helper to dump/inspect the workspace's
role+rolePermissionFlag+permissionFlagstate pre-upgrade? - Is there a CLI helper to reset failed upgrade state after a failed attempt, short of snapshot restore? (Failure #1 left the DB stuck in
Failedstate — only snapshot rollback recovered.) - Would a sanitized DB dump of the v2.4.0 state help as a reproducer?
Current state: rolled back to v2.4.0, fully functional, snapshot of v2.4.0 state preserved for reproduction if needed.
cc @prastoin (per Discord conversation)
Workspace cache services query columns or properties that do not exist yet because cache recomputation runs before schema migrations during upgrades.
- packages/twenty-server/src/database/typeorm/core/migrations/common
- packages/twenty-server/src/engine/workspace-manager/workspace-migration
- packages/twenty-server/src/engine/core-modules/workspace-cache/workspace-flat-view-filter-map-cache.service.ts
- packages/twenty-server/src/engine/core-modules/workspace-cache/workspace-roles-permissions-cache.service.ts
- packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils