twentyhq/twenty · error · Error

Migration failed for workspace ${workspaceId} while healing

Error message

Migration failed for workspace ${workspaceId} while healing standard relation field labels/icons:\n${failureDetails}

What it means

Thrown by the 2.14 workspace upgrade command that heals standard relation field labels/icons when WorkspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration returns status 'fail'. The failureDetails string is assembled from result.report, listing each failedValidation's metadataName, universal identifier, and per-error code/message. It means one or more standard relation fields failed validation during the system build (isSystemBuild: true) that permits mutating system-owned label/icon.

Source

Thrown at packages/twenty-server/src/database/commands/upgrade-version-command/2-14/2-14-workspace-command-1799000040000-fix-standard-relation-field-labels-icons.command.ts:157

      );

    if (result.status === 'fail') {
      const failureDetails = Object.values(result.report)
        .flat()
        .map((failedValidation) => {
          const errorMessages = failedValidation.errors
            .map((error) => `${error.code}: ${error.message}`)
            .join('; ');

          return `[${failedValidation.metadataName}] ${failedValidation.flatEntityMinimalInformation.universalIdentifier ?? failedValidation.flatEntityMinimalInformation.id ?? 'unknown'} -> ${errorMessages}`;
        })
        .join('\n');

      this.logger.error(
        `Migration build failed for workspace ${workspaceId} while healing standard relation field labels/icons:\n${failureDetails}`,
      );

      throw new Error(
        `Migration failed for workspace ${workspaceId} while healing standard relation field labels/icons:\n${failureDetails}`,
      );
    }

    this.logger.log(
      `Healed ${fieldsToUpdate.length} standard relation field(s) for workspace ${workspaceId}`,
    );
  }
}

View on GitHub (pinned to 1f5dd2bbd2)

Solutions

  1. Read the logged failureDetails (printed just before the throw) to see exactly which metadataName + universal identifier + error codes are failing, and correct those core.fieldMetadata rows.
  2. Verify the standard metadata seed bundled in the running image matches the version the workspace was last upgraded to; rebuild/redeploy the server image if it is stale.
  3. Re-run with the command's isDryRun path (or wrap the call) to confirm the computed fieldsToUpdate and the standard counterparts line up before applying.
  4. If a single workspace is irreparable, isolate it (mark suspended) so the runner continues with other workspaces, then repair its fieldMetadata rows manually and re-run.

Example fix

// before: standard relation field row drifted
core.fieldMetadata: { label: 'Old Label', icon: null }
// after: align with standard seed, then re-run upgrade
core.fieldMetadata: { label: 'Company' /* matches standard */, icon: 'IconBuilding' }
Defensive patterns

Strategy: validation

Validate before calling

// Before running the upgrade, confirm standard relation fields in the workspace
// match the standard seed for label/icon (the only fields this command mutates).
const drifted = await dataSource.query(`
  SELECT f."id", f."label", f."icon", s."label" AS std_label, s."icon" AS std_icon
  FROM core."fieldMetadata" f
  JOIN core."objectMetadata" o ON o."id" = f."objectMetadataId"
  JOIN metadata."standardFieldMetadata" s ON s."id" = f."standardFieldMetadataId"
  WHERE o."isCustom" = false AND f."type" IN ('RELATION')
    AND (f."label" IS DISTINCT FROM s."label" OR f."icon" IS DISTINCT FROM s."icon")
`);
// empty => command will no-op or succeed; non-empty => review rows first

Type guard

function isValidationFailReport(
  r: unknown,
): r is { status: 'fail'; report: Record<string, Array<{ metadataName: string; errors: Array<{ code: string; message: string }>; flatEntityMinimalInformation: { universalIdentifier?: string; id?: string } }>> } {
  return typeof r === 'object' && r !== null && (r as any).status === 'fail' && typeof (r as any).report === 'object';
}

Try / catch

// Workspace commands run inside the upgrade runner; wrap the runner invocation
// and record which workspace/command failed, then continue or stop per policy.
try {
  await upgradeRunner.runWorkspaceCommand(workspaceId, command);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Migration failed for workspace')) {
    await upgradeAudit.recordFailure(workspaceId, command.id, err.message);
    throw err; // surface to runner; do not swallow
  }
  throw err;
}

Prevention

When it happens

Trigger: Running database:migrate:prod (or the workspace upgrade runner) on a workspace whose core.fieldMetadata rows for standard relation fields have drifted from the standard definition (mismatched label/icon, missing standard counterpart, or conflicting overrides). A previous partial migration, a hand-edited fieldMetadata row, or a stale standard metadata seed will cause validation to emit codes in failedValidation.errors and yield status 'fail'.

Common situations: Upgrading a long-running workspace across many versions where a prior command partially mutated labels; restoring a DB backup taken mid-migration; running a custom script that updated standard relation field label/icon outside the migration framework; standard metadata seed in the code image being newer than what the workspace's data expects.

Related errors


AI-assisted analysis of twentyhq/twenty@1f5dd2bbd2 (2026-08-12). Data as JSON: /api/errors/65fb0bd08e84e80a. Report an issue: GitHub.