fix(server): make workflow-core + person-avatar upgrade commands v2-safe - #24724
Conversation
…de commands With IS_ORM_V2_READ_PATH_ENABLED removed, getRepository routes to the v2 datasource, which throws TwentyOrmV2Exception(UNKNOWN_OBJECT) — not EntityMetadataNotFoundError — when a workspace upgrading from before an object existed lacks that object. The workflow-core backfill/repair commands only caught the v1 error, so the cross-version upgrade smoke test failed at BackfillWorkflowVersionToCore with 'Object workflowVersion does not exist'. Add a shared isWorkspaceObjectNotFoundError guard covering both error types and use it in the four affected upgrade commands. (cherry picked from commit f4125df)
… v2 datasource MigratePersonAvatarUrlToAvatarFile read persons via the iterator's raw v1 GlobalWorkspaceDataSource (dataSource.getRepository). With the flag removed, computeForCache returns [] unconditionally, so that path's getMetadata throws EntityMetadataNotFoundError even when the person object exists — failing the cross-version upgrade on the seed workspace. Read through globalWorkspaceOrmManager .getRepository (which routes to the v2 datasource) instead. (cherry picked from commit b694880)
|
🚀 Preview Environment Ready! Your preview environment is available at: https://randy-social-republic-asp.trycloudflare.com This environment will automatically shut down after 5 hours. |
Greptile SummaryThe PR makes workflow and person-avatar upgrade commands compatible with the v2 workspace ORM.
Confidence Score: 5/5The PR appears safe to merge, with no actionable correctness or security failures identified. The missing-object predicate is limited to the two expected ORM error representations, and the avatar command uses a correctly scoped, compatible v2 repository whose file updates satisfy the existing validation contract. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Workspace upgrade command] --> B{Target object exists?}
B -->|No: v1 or v2 missing-object error| C[Skip workspace safely]
B -->|Yes| D[Acquire workspace repository]
D --> E[Run workflow backfill or avatar migration]
E --> F[Complete workspace upgrade]
Reviews (1): Last reviewed commit: "fix(server): route person-avatar backfil..." | Re-trigger Greptile |
🟡 Standard review · 1 finding
High-level — Clean DRY consolidation of a duplicated not-found predicate into a shared util, extended for the v2 datasource; no schema, migration, or public-surface change and well under the size limit. 💬 1 inline comment on the diff. Reviewed against the |
| // The v1 GlobalWorkspaceDataSource throws EntityMetadataNotFoundError, while the v2 | ||
| // datasource throws TwentyOrmV2Exception(UNKNOWN_OBJECT). Backfill commands must treat | ||
| // both as "object not provisioned yet" and skip rather than fail the upgrade. | ||
| export const isWorkspaceObjectNotFoundError = (error: unknown): boolean => |
There was a problem hiding this comment.
🟡 Nit · Low-level · Backend tests / regression test
New pure util with a branch has no unit spec, leaving the v2 UNKNOWN_OBJECT path untested
The whole point of the change — treating TwentyOrmV2Exception(UNKNOWN_OBJECT) as "object not provisioned" — is asserted nowhere; the pre-existing 2-28 spec only exercises the v1 EntityMetadataNotFoundError branch. Add an is-workspace-object-not-found-error.util.spec.ts covering both error types and a non-matching error.
There was a problem hiding this comment.
Pull request overview
This PR aims to make several workspace upgrade commands compatible with the post-#24692 world where ORM v2 is the only supported workspace entity read path, by (a) broadening “object missing” guards to recognize the ORM v2 error type and (b) routing one command’s workspace reads through GlobalWorkspaceOrmManager (v2) instead of the iterator’s v1 GlobalWorkspaceDataSource.
Changes:
- Add
isWorkspaceObjectNotFoundErrorto treat both v1EntityMetadataNotFoundErrorand v2TwentyOrmV2ExceptionCode.UNKNOWN_OBJECTas “object not provisioned”. - Update multiple workflow-related upgrade commands to use the shared predicate in their missing-object guards.
- Update
MigratePersonAvatarUrlToAvatarFileto readpersonviaGlobalWorkspaceOrmManager.getRepository(v2).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/twenty-server/src/database/commands/upgrade-version-command/utils/is-workspace-object-not-found-error.util.ts | Introduces shared predicate to classify “workspace object missing” across ORM v1/v2 error types. |
| packages/twenty-server/src/database/commands/upgrade-version-command/2-28/2-28-workspace-command-1785600000000-repair-orphan-core-workflow-versions.command.ts | Switches the skip-guard to the shared predicate. |
| packages/twenty-server/src/database/commands/upgrade-version-command/2-23/2-23-workspace-command-1784286707000-backfill-workflow-core-links.command.ts | Switches the skip-guard to the shared predicate. |
| packages/twenty-server/src/database/commands/upgrade-version-command/2-22/2-22-workspace-command-1784193207000-backfill-workflow-version-core-links.command.ts | Switches the skip-guard to the shared predicate. |
| packages/twenty-server/src/database/commands/upgrade-version-command/2-22/2-22-workspace-command-1783960128000-migrate-person-avatar-url-to-avatar-file.command.ts | Routes person reads through GlobalWorkspaceOrmManager.getRepository (v2) instead of v1 iterator datasource. |
| packages/twenty-server/src/database/commands/upgrade-version-command/2-20/2-20-workspace-command-1783526282685-backfill-workflow-version-to-core.command.ts | Switches the skip-guard to the shared predicate for v2 error compatibility. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| workspaceWorkflows = await workflowRepository.find(); | ||
| } catch (error) { | ||
| if (error instanceof EntityMetadataNotFoundError) { | ||
| if (isWorkspaceObjectNotFoundError(error)) { | ||
| this.logger.log( | ||
| `workflow object does not exist for workspace ${workspaceId}, skipping`, |
| workspaceWorkflowVersions = await workflowVersionRepository.find(); | ||
| } catch (error) { | ||
| if (error instanceof EntityMetadataNotFoundError) { | ||
| if (isWorkspaceObjectNotFoundError(error)) { | ||
| this.logger.log( | ||
| `workflowVersion object does not exist for workspace ${workspaceId}, skipping`, |
| .count(); | ||
| } catch (error) { | ||
| if (error instanceof EntityMetadataNotFoundError) { | ||
| if (isWorkspaceObjectNotFoundError(error)) { | ||
| return; | ||
| } |
| export const isWorkspaceObjectNotFoundError = (error: unknown): boolean => | ||
| error instanceof EntityMetadataNotFoundError || | ||
| (error instanceof TwentyOrmV2Exception && | ||
| error.code === TwentyOrmV2ExceptionCode.UNKNOWN_OBJECT); |
Context
#24692removedIS_ORM_V2_READ_PATH_ENABLEDand collapsed to the v2 path:GlobalWorkspaceOrmManager.getRepositorynow always routes to the v2 datasource, andWorkspaceORMEntityMetadatasCacheService.computeForCachereturns[]unconditionally (the v1GlobalWorkspaceDataSourcemetadata is no longer built).Several workspace upgrade commands still relied on v1 behavior and break under this. It wasn't caught because the cross-version-upgrade smoke test only runs on release tags — cutting one off
mainsurfaced it (staging-cifailed atBackfillWorkflowVersionToCoreand thenMigratePersonAvatarUrlToAvatarFile).Two incompatibilities fixed
1. Missing-object guards only caught the v1 error type. Backfill/repair commands that skip when a workspace predates an object caught
EntityMetadataNotFoundError(thrown by v1getMetadata). The v2 datasource instead throwsTwentyOrmV2Exception(UNKNOWN_OBJECT), so the guard missed it and the upgrade failed. Added a sharedisWorkspaceObjectNotFoundErrorpredicate covering both error types and used it in the four affected commands:2-20 backfill-workflow-version-to-core2-22 backfill-workflow-version-core-links2-23 backfill-workflow-core-links2-28 repair-orphan-core-workflow-versions2. Reading through the iterator's raw v1 datasource.
MigratePersonAvatarUrlToAvatarFileread persons viadataSource.getRepository('person'), wheredataSourceis the iterator'sGlobalWorkspaceDataSource. SincecomputeForCachenow returns[], that path throwsEntityMetadataNotFoundError: No metadata for "person"even when the person object exists (it failed on the seed workspace). Routed the read throughglobalWorkspaceOrmManager.getRepository(v2) instead — the command already runs inside the iterator'sexecuteInWorkspaceContext.Validation
Validated on the
v2.34.xrelease line: these exact fixes tookstaging-ci(build + cross-version-upgrade smoke test) from failing to green. This PR brings them tomain.Note: the 2-9 AI-model-preferences command also matches
dataSource.getRepositorybut reads a core entity (KeyValuePairEntity) on the core datasource, so it's unaffected and left as-is.