From 52a074e0418ca160aa09ba9ba66bff7abafe45cb Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Mon, 22 Jun 2020 21:28:02 +0200
Subject: [PATCH] refactor: ExternalHostError (#6563)

---
 lib/config/presets/github/index.ts            |  4 +--
 .../gitlab/__snapshots__/index.spec.ts.snap   |  2 +-
 lib/config/presets/gitlab/index.spec.ts       |  6 ++---
 lib/config/presets/gitlab/index.ts            |  4 +--
 lib/config/presets/index.ts                   | 12 +++------
 lib/constants/error-messages.ts               |  5 ++--
 lib/datasource/cdnjs/index.spec.ts            | 14 +++++-----
 lib/datasource/cdnjs/index.ts                 |  7 ++---
 lib/datasource/common.ts                      | 17 ------------
 .../crate/__snapshots__/index.spec.ts.snap    |  2 +-
 lib/datasource/crate/index.ts                 | 10 +++----
 .../dart/__snapshots__/index.spec.ts.snap     |  2 +-
 lib/datasource/dart/index.ts                  |  5 ++--
 lib/datasource/docker/index.spec.ts           |  6 ++---
 lib/datasource/docker/index.ts                | 25 ++++++++---------
 .../galaxy/__snapshots__/index.spec.ts.snap   |  2 +-
 lib/datasource/galaxy/index.ts                | 10 +++----
 lib/datasource/gradle-version/index.ts        |  5 ++--
 .../helm/__snapshots__/index.spec.ts.snap     |  2 +-
 lib/datasource/helm/index.ts                  |  5 ++--
 lib/datasource/hex/index.spec.ts              |  6 ++---
 lib/datasource/hex/index.ts                   |  5 ++--
 lib/datasource/index.spec.ts                  | 16 +++++------
 lib/datasource/index.ts                       | 10 +++----
 lib/datasource/maven/index.spec.ts            |  6 ++---
 lib/datasource/maven/util.ts                  |  4 +--
 lib/datasource/npm/get.spec.ts                |  4 +--
 lib/datasource/npm/get.ts                     |  5 ++--
 lib/datasource/npm/index.spec.ts              |  6 ++---
 lib/datasource/packagist/index.ts             |  7 ++---
 lib/datasource/pod/index.spec.ts              |  3 ++-
 lib/datasource/pod/index.ts                   |  3 ++-
 lib/datasource/repology/index.spec.ts         |  8 +++---
 lib/datasource/repology/index.ts              |  7 ++---
 lib/datasource/ruby-version/index.ts          |  5 ++--
 lib/datasource/rubygems/get-rubygems-org.ts   |  5 ++--
 lib/datasource/terraform-module/index.ts      |  5 ++--
 lib/manager/gradle/index.ts                   |  6 ++---
 lib/manager/npm/post-update/index.ts          | 25 ++++++++++-------
 lib/manager/npm/post-update/yarn.ts           |  5 ++--
 lib/platform/git/storage.ts                   |  6 ++---
 lib/platform/github/index.ts                  | 14 +++++-----
 lib/types/error.ts                            | 19 +++++++++++++
 lib/util/http/github.spec.ts                  | 12 ++++-----
 lib/util/http/github.ts                       | 12 ++++-----
 lib/util/http/gitlab.ts                       |  8 +++---
 lib/workers/branch/index.ts                   | 15 +++--------
 lib/workers/pr/index.ts                       |  4 +--
 lib/workers/repository/error.spec.ts          | 20 +++++++-------
 lib/workers/repository/error.ts               | 27 ++++++-------------
 lib/workers/repository/init/config.ts         | 11 ++++----
 51 files changed, 211 insertions(+), 223 deletions(-)
 create mode 100644 lib/types/error.ts

diff --git a/lib/config/presets/github/index.ts b/lib/config/presets/github/index.ts
index 7363c3810d..2a781f073a 100644
--- a/lib/config/presets/github/index.ts
+++ b/lib/config/presets/github/index.ts
@@ -1,6 +1,6 @@
-import { PLATFORM_FAILURE } from '../../../constants/error-messages';
 import { PLATFORM_TYPE_GITHUB } from '../../../constants/platforms';
 import { logger } from '../../../logger';
+import { ExternalHostError } from '../../../types/error';
 import { Http, HttpOptions } from '../../../util/http';
 import { Preset, PresetConfig } from '../common';
 import { PRESET_DEP_NOT_FOUND, fetchPreset } from '../util';
@@ -27,7 +27,7 @@ export async function fetchJSONFile(
     res = await http.getJson(url, opts);
   } catch (err) {
     // istanbul ignore if: not testable with nock
-    if (err.message === PLATFORM_FAILURE) {
+    if (err instanceof ExternalHostError) {
       throw err;
     }
     logger.debug(
diff --git a/lib/config/presets/gitlab/__snapshots__/index.spec.ts.snap b/lib/config/presets/gitlab/__snapshots__/index.spec.ts.snap
index a04e3707f8..b79273bd38 100644
--- a/lib/config/presets/gitlab/__snapshots__/index.spec.ts.snap
+++ b/lib/config/presets/gitlab/__snapshots__/index.spec.ts.snap
@@ -70,7 +70,7 @@ Array [
 ]
 `;
 
-exports[`config/presets/gitlab/index getPreset() throws platform-failure 1`] = `
+exports[`config/presets/gitlab/index getPreset() throws EXTERNAL_HOST_ERROR 1`] = `
 Array [
   Object {
     "headers": Object {
diff --git a/lib/config/presets/gitlab/index.spec.ts b/lib/config/presets/gitlab/index.spec.ts
index e180961dd3..2d48a835a5 100644
--- a/lib/config/presets/gitlab/index.spec.ts
+++ b/lib/config/presets/gitlab/index.spec.ts
@@ -1,6 +1,6 @@
 import * as httpMock from '../../../../test/httpMock';
 import { getName } from '../../../../test/util';
-import { PLATFORM_FAILURE } from '../../../constants/error-messages';
+import { EXTERNAL_HOST_ERROR } from '../../../constants/error-messages';
 import { PRESET_DEP_NOT_FOUND } from '../util';
 import * as gitlab from '.';
 
@@ -18,14 +18,14 @@ describe(getName(__filename), () => {
   });
 
   describe('getPreset()', () => {
-    it('throws platform-failure', async () => {
+    it('throws EXTERNAL_HOST_ERROR', async () => {
       httpMock.scope(gitlabApiHost).get(`${basePath}/branches`).reply(500);
       await expect(
         gitlab.getPreset({
           packageName: 'some/repo',
           presetName: 'non-default',
         })
-      ).rejects.toThrow(PLATFORM_FAILURE);
+      ).rejects.toThrow(EXTERNAL_HOST_ERROR);
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
 
diff --git a/lib/config/presets/gitlab/index.ts b/lib/config/presets/gitlab/index.ts
index 7bec62cc2f..a5f7eb7821 100644
--- a/lib/config/presets/gitlab/index.ts
+++ b/lib/config/presets/gitlab/index.ts
@@ -1,5 +1,5 @@
-import { PLATFORM_FAILURE } from '../../../constants/error-messages';
 import { logger } from '../../../logger';
+import { ExternalHostError } from '../../../types/error';
 import type { GitLabBranch } from '../../../types/platform/gitlab';
 import { GitlabHttp } from '../../../util/http/gitlab';
 import { Preset, PresetConfig } from '../common';
@@ -42,7 +42,7 @@ export async function fetchJSONFile(
     const url = `${endpoint}projects/${urlEncodedRepo}/repository/files/${urlEncodedPkgName}/raw?ref=${defautlBranchName}`;
     return (await gitlabApi.getJson<Preset>(url)).body;
   } catch (err) {
-    if (err.message === PLATFORM_FAILURE) {
+    if (err instanceof ExternalHostError) {
       throw err;
     }
     logger.debug(
diff --git a/lib/config/presets/index.ts b/lib/config/presets/index.ts
index 144d068c95..3a9eea8fdf 100644
--- a/lib/config/presets/index.ts
+++ b/lib/config/presets/index.ts
@@ -1,10 +1,7 @@
 import is from '@sindresorhus/is';
-import {
-  CONFIG_VALIDATION,
-  DATASOURCE_FAILURE,
-  PLATFORM_FAILURE,
-} from '../../constants/error-messages';
+import { CONFIG_VALIDATION } from '../../constants/error-messages';
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import { regEx } from '../../util/regex';
 import { RenovateConfig } from '../common';
 import * as massage from '../massage';
@@ -207,10 +204,7 @@ export async function resolveConfigPresets(
         } catch (err) {
           logger.debug({ preset, err }, 'Preset fetch error');
           // istanbul ignore if
-          if (
-            err.message === PLATFORM_FAILURE ||
-            err.message === DATASOURCE_FAILURE
-          ) {
+          if (err instanceof ExternalHostError) {
             throw err;
           }
           const error = new Error(CONFIG_VALIDATION);
diff --git a/lib/constants/error-messages.ts b/lib/constants/error-messages.ts
index b1bb53ef6d..12ad1164c1 100644
--- a/lib/constants/error-messages.ts
+++ b/lib/constants/error-messages.ts
@@ -5,7 +5,6 @@ export const SYSTEM_INSUFFICIENT_MEMORY = 'out-of-memory';
 // Platform Error
 export const PLATFORM_AUTHENTICATION_ERROR = 'authentication-error';
 export const PLATFORM_BAD_CREDENTIALS = 'bad-credentials';
-export const PLATFORM_FAILURE = 'platform-failure';
 export const PLATFORM_GPG_FAILED = 'gpg-failed';
 export const PLATFORM_INTEGRATION_UNAUTHORIZED = 'integration-unauthorized';
 export const PLATFORM_NOT_FOUND = 'platform-not-found';
@@ -34,8 +33,8 @@ export const REPOSITORY_UNINITIATED = 'uninitiated';
 export const MANAGER_LOCKFILE_ERROR = 'lockfile-error';
 export const MANAGER_NO_PACKAGE_FILES = 'no-package-files';
 
-// Datasource error
-export const DATASOURCE_FAILURE = 'registry-failure';
+// Host error
+export const EXTERNAL_HOST_ERROR = 'external-host-error';
 
 // Worker Error
 export const WORKER_FILE_UPDATE_FAILED = 'update-failure';
diff --git a/lib/datasource/cdnjs/index.spec.ts b/lib/datasource/cdnjs/index.spec.ts
index 976b61003c..2ff92f7cbf 100644
--- a/lib/datasource/cdnjs/index.spec.ts
+++ b/lib/datasource/cdnjs/index.spec.ts
@@ -1,7 +1,7 @@
 import fs from 'fs';
 import { getPkgReleases } from '..';
 import * as httpMock from '../../../test/httpMock';
-import { DATASOURCE_FAILURE } from '../../constants/error-messages';
+import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages';
 import { id as datasource } from '.';
 
 let res1 = fs.readFileSync(
@@ -36,14 +36,14 @@ describe('datasource/cdnjs', () => {
       httpMock.scope(baseUrl).get(pathFor('foo/bar')).reply(200, null);
       await expect(
         getPkgReleases({ datasource, depName: 'foo/bar' })
-      ).rejects.toThrow(DATASOURCE_FAILURE);
+      ).rejects.toThrow(EXTERNAL_HOST_ERROR);
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('throws for error', async () => {
       httpMock.scope(baseUrl).get(pathFor('foo/bar')).replyWithError('error');
       await expect(
         getPkgReleases({ datasource, depName: 'foo/bar' })
-      ).rejects.toThrow(DATASOURCE_FAILURE);
+      ).rejects.toThrow(EXTERNAL_HOST_ERROR);
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('returns null for 404', async () => {
@@ -70,28 +70,28 @@ describe('datasource/cdnjs', () => {
       httpMock.scope(baseUrl).get(pathFor('foo/bar')).reply(401);
       await expect(
         getPkgReleases({ datasource, depName: 'foo/bar' })
-      ).rejects.toThrow(DATASOURCE_FAILURE);
+      ).rejects.toThrow(EXTERNAL_HOST_ERROR);
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('throws for 429', async () => {
       httpMock.scope(baseUrl).get(pathFor('foo/bar')).reply(429);
       await expect(
         getPkgReleases({ datasource, depName: 'foo/bar' })
-      ).rejects.toThrow(DATASOURCE_FAILURE);
+      ).rejects.toThrow(EXTERNAL_HOST_ERROR);
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('throws for 5xx', async () => {
       httpMock.scope(baseUrl).get(pathFor('foo/bar')).reply(502);
       await expect(
         getPkgReleases({ datasource, depName: 'foo/bar' })
-      ).rejects.toThrow(DATASOURCE_FAILURE);
+      ).rejects.toThrow(EXTERNAL_HOST_ERROR);
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('returns null for unknown error', async () => {
       httpMock.scope(baseUrl).get(pathFor('foo/bar')).replyWithError('error');
       await expect(
         getPkgReleases({ datasource, depName: 'foo/bar' })
-      ).rejects.toThrow(DATASOURCE_FAILURE);
+      ).rejects.toThrow(EXTERNAL_HOST_ERROR);
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('processes real data', async () => {
diff --git a/lib/datasource/cdnjs/index.ts b/lib/datasource/cdnjs/index.ts
index 5b5f7f2026..c408bd0d14 100644
--- a/lib/datasource/cdnjs/index.ts
+++ b/lib/datasource/cdnjs/index.ts
@@ -1,7 +1,8 @@
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import { Http } from '../../util/http';
 import { CachePromise, cacheAble } from '../cache';
-import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common';
+import { GetReleasesConfig, ReleaseResult } from '../common';
 
 export const id = 'cdnjs';
 
@@ -60,7 +61,7 @@ export async function getReleases({
       logger.debug({ library }, 'cdnjs library not found');
       return null;
     }
-    // Throw a DatasourceError for all other types of errors
-    throw new DatasourceError(err);
+    // Throw an ExternalHostError for all other types of errors
+    throw new ExternalHostError(err);
   }
 }
diff --git a/lib/datasource/common.ts b/lib/datasource/common.ts
index 1d4be8b8c4..0f31ca9cb2 100644
--- a/lib/datasource/common.ts
+++ b/lib/datasource/common.ts
@@ -1,5 +1,3 @@
-import { DATASOURCE_FAILURE } from '../constants/error-messages';
-
 export interface Config {
   datasource?: string;
   depName?: string;
@@ -80,18 +78,3 @@ export interface Datasource {
   defaultConfig?: object;
   registryStrategy?: 'first' | 'hunt' | 'merge';
 }
-
-export class DatasourceError extends Error {
-  err: Error;
-
-  datasource?: string;
-
-  lookupName?: string;
-
-  constructor(err: Error) {
-    super(DATASOURCE_FAILURE);
-    // Set the prototype explicitly: https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
-    Object.setPrototypeOf(this, DatasourceError.prototype);
-    this.err = err;
-  }
-}
diff --git a/lib/datasource/crate/__snapshots__/index.spec.ts.snap b/lib/datasource/crate/__snapshots__/index.spec.ts.snap
index 63116b90b9..f5d2a6e725 100644
--- a/lib/datasource/crate/__snapshots__/index.spec.ts.snap
+++ b/lib/datasource/crate/__snapshots__/index.spec.ts.snap
@@ -764,7 +764,7 @@ Array [
 ]
 `;
 
-exports[`datasource/crate getReleases throws for 5xx 1`] = `[Error: registry-failure]`;
+exports[`datasource/crate getReleases throws for 5xx 1`] = `[Error: external-host-error]`;
 
 exports[`datasource/crate getReleases throws for 5xx 2`] = `
 Array [
diff --git a/lib/datasource/crate/index.ts b/lib/datasource/crate/index.ts
index fe948c1ab7..40134c936d 100644
--- a/lib/datasource/crate/index.ts
+++ b/lib/datasource/crate/index.ts
@@ -1,12 +1,8 @@
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import * as globalCache from '../../util/cache/global';
 import { Http } from '../../util/http';
-import {
-  DatasourceError,
-  GetReleasesConfig,
-  Release,
-  ReleaseResult,
-} from '../common';
+import { GetReleasesConfig, Release, ReleaseResult } from '../common';
 
 export const id = 'crate';
 
@@ -105,7 +101,7 @@ export async function getReleases({
       err.statusCode === 429 ||
       (err.statusCode >= 500 && err.statusCode < 600)
     ) {
-      throw new DatasourceError(err);
+      throw new ExternalHostError(err);
     }
     logger.warn({ err, lookupName }, 'crates.io lookup failure: Unknown error');
     return null;
diff --git a/lib/datasource/dart/__snapshots__/index.spec.ts.snap b/lib/datasource/dart/__snapshots__/index.spec.ts.snap
index 39f878f58f..856d329c5d 100644
--- a/lib/datasource/dart/__snapshots__/index.spec.ts.snap
+++ b/lib/datasource/dart/__snapshots__/index.spec.ts.snap
@@ -144,7 +144,7 @@ Array [
 ]
 `;
 
-exports[`datasource/dart getReleases throws for 5xx 1`] = `[Error: registry-failure]`;
+exports[`datasource/dart getReleases throws for 5xx 1`] = `[Error: external-host-error]`;
 
 exports[`datasource/dart getReleases throws for 5xx 2`] = `
 Array [
diff --git a/lib/datasource/dart/index.ts b/lib/datasource/dart/index.ts
index e080ef053f..4043f1df5b 100644
--- a/lib/datasource/dart/index.ts
+++ b/lib/datasource/dart/index.ts
@@ -1,6 +1,7 @@
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import { Http, HttpResponse } from '../../util/http';
-import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common';
+import { GetReleasesConfig, ReleaseResult } from '../common';
 
 export const id = 'dart';
 
@@ -33,7 +34,7 @@ export async function getReleases({
       err.statusCode === 429 ||
       (err.statusCode >= 500 && err.statusCode < 600)
     ) {
-      throw new DatasourceError(err);
+      throw new ExternalHostError(err);
     }
     logger.warn(
       { err, lookupName },
diff --git a/lib/datasource/docker/index.spec.ts b/lib/datasource/docker/index.spec.ts
index 7d2c09c7ff..2f69ffa7d3 100644
--- a/lib/datasource/docker/index.spec.ts
+++ b/lib/datasource/docker/index.spec.ts
@@ -2,7 +2,7 @@ import AWS from 'aws-sdk';
 import AWSMock from 'aws-sdk-mock';
 import { getDigest, getPkgReleases } from '..';
 import * as httpMock from '../../../test/httpMock';
-import { DATASOURCE_FAILURE } from '../../constants/error-messages';
+import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages';
 import * as _hostRules from '../../util/host-rules';
 import * as docker from '.';
 
@@ -331,13 +331,13 @@ describe('api/docker', () => {
       httpMock.scope(baseUrl).get('/').replyWithError({ statusCode: 429 });
       await expect(
         getDigest({ datasource: 'docker', depName: 'some-dep' }, 'latest')
-      ).rejects.toThrow(Error(DATASOURCE_FAILURE));
+      ).rejects.toThrow(EXTERNAL_HOST_ERROR);
     });
     it('should throw error for 5xx', async () => {
       httpMock.scope(baseUrl).get('/').replyWithError({ statusCode: 504 });
       await expect(
         getDigest({ datasource: 'docker', depName: 'some-dep' }, 'latest')
-      ).rejects.toThrow(Error(DATASOURCE_FAILURE));
+      ).rejects.toThrow(EXTERNAL_HOST_ERROR);
     });
   });
   describe('getReleases', () => {
diff --git a/lib/datasource/docker/index.ts b/lib/datasource/docker/index.ts
index 34536479d3..7a9e57c419 100644
--- a/lib/datasource/docker/index.ts
+++ b/lib/datasource/docker/index.ts
@@ -6,10 +6,11 @@ import parseLinkHeader from 'parse-link-header';
 import wwwAuthenticate from 'www-authenticate';
 import { logger } from '../../logger';
 import { HostRule } from '../../types';
+import { ExternalHostError } from '../../types/error';
 import * as globalCache from '../../util/cache/global';
 import * as hostRules from '../../util/host-rules';
 import { Http, HttpResponse } from '../../util/http';
-import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common';
+import { GetReleasesConfig, ReleaseResult } from '../common';
 
 // TODO: add got typings when available
 // TODO: replace www-authenticate with https://www.npmjs.com/package/auth-header ?
@@ -220,14 +221,14 @@ async function getAuthHeaders(
     }
     // prettier-ignore
     if (err.name === 'RequestError' && registry.endsWith('docker.io')) { // lgtm [js/incomplete-url-substring-sanitization]
-      throw new DatasourceError(err);
+      throw new ExternalHostError(err);
     }
     // prettier-ignore
     if (err.statusCode === 429 && registry.endsWith('docker.io')) { // lgtm [js/incomplete-url-substring-sanitization]
-      throw new DatasourceError(err);
+      throw new ExternalHostError(err);
     }
     if (err.statusCode >= 500 && err.statusCode < 600) {
-      throw new DatasourceError(err);
+      throw new ExternalHostError(err);
     }
     logger.warn(
       { registry, dockerRepository: repository, err },
@@ -267,7 +268,7 @@ async function getManifestResponse(
     });
     return manifestResponse;
   } catch (err) /* istanbul ignore next */ {
-    if (err instanceof DatasourceError) {
+    if (err instanceof ExternalHostError) {
       throw err;
     }
     if (err.statusCode === 401) {
@@ -292,10 +293,10 @@ async function getManifestResponse(
     }
     // prettier-ignore
     if (err.statusCode === 429 && registry.endsWith('docker.io')) { // lgtm [js/incomplete-url-substring-sanitization]
-      throw new DatasourceError(err);
+      throw new ExternalHostError(err);
     }
     if (err.statusCode >= 500 && err.statusCode < 600) {
-      throw new DatasourceError(err);
+      throw new ExternalHostError(err);
     }
     if (err.code === 'ETIMEDOUT') {
       logger.debug(
@@ -356,7 +357,7 @@ export async function getDigest(
       logger.debug({ digest }, 'Got docker digest');
     }
   } catch (err) /* istanbul ignore next */ {
-    if (err instanceof DatasourceError) {
+    if (err instanceof ExternalHostError) {
       throw err;
     }
     logger.debug(
@@ -413,7 +414,7 @@ async function getTags(
     await globalCache.set(cacheNamespace, cacheKey, tags, cacheMinutes);
     return tags;
   } catch (err) /* istanbul ignore next */ {
-    if (err instanceof DatasourceError) {
+    if (err instanceof ExternalHostError) {
       throw err;
     }
     logger.debug(
@@ -441,14 +442,14 @@ async function getTags(
         { registry, dockerRepository: repository, err },
         'docker registry failure: too many requests'
       );
-      throw new DatasourceError(err);
+      throw new ExternalHostError(err);
     }
     if (err.statusCode >= 500 && err.statusCode < 600) {
       logger.warn(
         { registry, dockerRepository: repository, err },
         'docker registry failure: internal error'
       );
-      throw new DatasourceError(err);
+      throw new ExternalHostError(err);
     }
     if (err.code === 'ETIMEDOUT') {
       logger.debug(
@@ -543,7 +544,7 @@ async function getLabels(
     await globalCache.set(cacheNamespace, cacheKey, labels, cacheMinutes);
     return labels;
   } catch (err) {
-    if (err instanceof DatasourceError) {
+    if (err instanceof ExternalHostError) {
       throw err;
     }
     if (err.statusCode === 400 || err.statusCode === 401) {
diff --git a/lib/datasource/galaxy/__snapshots__/index.spec.ts.snap b/lib/datasource/galaxy/__snapshots__/index.spec.ts.snap
index 1ecdb5c221..3e2f68cc8b 100644
--- a/lib/datasource/galaxy/__snapshots__/index.spec.ts.snap
+++ b/lib/datasource/galaxy/__snapshots__/index.spec.ts.snap
@@ -119,7 +119,7 @@ Array [
 ]
 `;
 
-exports[`datasource/galaxy getReleases throws for 5xx 1`] = `[Error: registry-failure]`;
+exports[`datasource/galaxy getReleases throws for 5xx 1`] = `[Error: external-host-error]`;
 
 exports[`datasource/galaxy getReleases throws for 5xx 2`] = `
 Array [
diff --git a/lib/datasource/galaxy/index.ts b/lib/datasource/galaxy/index.ts
index ce6b9e8964..a76942dfd4 100644
--- a/lib/datasource/galaxy/index.ts
+++ b/lib/datasource/galaxy/index.ts
@@ -1,12 +1,8 @@
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import * as globalCache from '../../util/cache/global';
 import { Http } from '../../util/http';
-import {
-  DatasourceError,
-  GetReleasesConfig,
-  Release,
-  ReleaseResult,
-} from '../common';
+import { GetReleasesConfig, Release, ReleaseResult } from '../common';
 
 export const id = 'galaxy';
 
@@ -101,7 +97,7 @@ export async function getReleases({
       err.statusCode === 429 ||
       (err.statusCode >= 500 && err.statusCode < 600)
     ) {
-      throw new DatasourceError(err);
+      throw new ExternalHostError(err);
     }
     return null;
   }
diff --git a/lib/datasource/gradle-version/index.ts b/lib/datasource/gradle-version/index.ts
index aab1d9d613..624bb27b5e 100644
--- a/lib/datasource/gradle-version/index.ts
+++ b/lib/datasource/gradle-version/index.ts
@@ -1,7 +1,8 @@
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import { Http } from '../../util/http';
 import { regEx } from '../../util/regex';
-import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common';
+import { GetReleasesConfig, ReleaseResult } from '../common';
 
 export const id = 'gradle-version';
 export const defaultRegistryUrls = ['https://services.gradle.org/versions/all'];
@@ -50,7 +51,7 @@ export async function getReleases({
       }));
   } catch (err) /* istanbul ignore next */ {
     if (err.host === 'services.gradle.org') {
-      throw new DatasourceError(err);
+      throw new ExternalHostError(err);
     }
     logger.debug({ err }, 'gradle-version err');
     return null;
diff --git a/lib/datasource/helm/__snapshots__/index.spec.ts.snap b/lib/datasource/helm/__snapshots__/index.spec.ts.snap
index 8ae2aea4bb..d348280ab6 100644
--- a/lib/datasource/helm/__snapshots__/index.spec.ts.snap
+++ b/lib/datasource/helm/__snapshots__/index.spec.ts.snap
@@ -244,7 +244,7 @@ Array [
 ]
 `;
 
-exports[`datasource/helm getReleases throws for 5xx 1`] = `[Error: registry-failure]`;
+exports[`datasource/helm getReleases throws for 5xx 1`] = `[Error: external-host-error]`;
 
 exports[`datasource/helm getReleases throws for 5xx 2`] = `
 Array [
diff --git a/lib/datasource/helm/index.ts b/lib/datasource/helm/index.ts
index 4b68427521..5ba8ba238f 100644
--- a/lib/datasource/helm/index.ts
+++ b/lib/datasource/helm/index.ts
@@ -1,10 +1,11 @@
 import yaml from 'js-yaml';
 
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import * as globalCache from '../../util/cache/global';
 import { Http } from '../../util/http';
 import { ensureTrailingSlash } from '../../util/url';
-import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common';
+import { GetReleasesConfig, ReleaseResult } from '../common';
 
 export const id = 'helm';
 
@@ -60,7 +61,7 @@ export async function getRepositoryData(
       err.statusCode === 429 ||
       (err.statusCode >= 500 && err.statusCode < 600)
     ) {
-      throw new DatasourceError(err);
+      throw new ExternalHostError(err);
     }
     // istanbul ignore if
     if (err.name === 'UnsupportedProtocolError') {
diff --git a/lib/datasource/hex/index.spec.ts b/lib/datasource/hex/index.spec.ts
index a751a20da5..14dabf83f2 100644
--- a/lib/datasource/hex/index.spec.ts
+++ b/lib/datasource/hex/index.spec.ts
@@ -1,7 +1,7 @@
 import fs from 'fs';
 import { getPkgReleases } from '..';
 import * as httpMock from '../../../test/httpMock';
-import { DATASOURCE_FAILURE } from '../../constants/error-messages';
+import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages';
 import * as _hostRules from '../../util/host-rules';
 import { id as datasource } from '.';
 
@@ -67,14 +67,14 @@ describe('datasource/hex', () => {
       httpMock.scope(baseUrl).get('/some_crate').reply(429);
       await expect(
         getPkgReleases({ datasource, depName: 'some_crate' })
-      ).rejects.toThrow(DATASOURCE_FAILURE);
+      ).rejects.toThrow(EXTERNAL_HOST_ERROR);
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('throws for 5xx', async () => {
       httpMock.scope(baseUrl).get('/some_crate').reply(502);
       await expect(
         getPkgReleases({ datasource, depName: 'some_crate' })
-      ).rejects.toThrow(DATASOURCE_FAILURE);
+      ).rejects.toThrow(EXTERNAL_HOST_ERROR);
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('returns null for unknown error', async () => {
diff --git a/lib/datasource/hex/index.ts b/lib/datasource/hex/index.ts
index dc614c97ee..c619cbffc0 100644
--- a/lib/datasource/hex/index.ts
+++ b/lib/datasource/hex/index.ts
@@ -1,6 +1,7 @@
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import { Http } from '../../util/http';
-import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common';
+import { GetReleasesConfig, ReleaseResult } from '../common';
 
 export const id = 'hex';
 
@@ -69,7 +70,7 @@ export async function getReleases({
       err.statusCode === 429 ||
       (err.statusCode >= 500 && err.statusCode < 600)
     ) {
-      throw new DatasourceError(err);
+      throw new ExternalHostError(err);
     }
 
     if (err.statusCode === 401) {
diff --git a/lib/datasource/index.spec.ts b/lib/datasource/index.spec.ts
index 974003a4b0..34aa6531a7 100644
--- a/lib/datasource/index.spec.ts
+++ b/lib/datasource/index.spec.ts
@@ -1,7 +1,7 @@
 import { mocked } from '../../test/util';
-import { DATASOURCE_FAILURE } from '../constants/error-messages';
+import { EXTERNAL_HOST_ERROR } from '../constants/error-messages';
+import { ExternalHostError } from '../types/error';
 import { loadModules } from '../util/modules';
-import { DatasourceError } from './common';
 import * as datasourceDocker from './docker';
 import * as datasourceGithubTags from './github-tags';
 import * as datasourceMaven from './maven';
@@ -131,9 +131,9 @@ describe('datasource/index', () => {
     });
     expect(res).not.toBeNull();
   });
-  it('hunts registries and aborts on DatasourceError', async () => {
+  it('hunts registries and aborts on ExternalHostError', async () => {
     packagistDatasource.getReleases.mockImplementationOnce(() => {
-      throw new DatasourceError(new Error());
+      throw new ExternalHostError(new Error());
     });
     await expect(
       datasource.getPkgReleases({
@@ -141,7 +141,7 @@ describe('datasource/index', () => {
         depName: 'something',
         registryUrls: ['https://reg1.com', 'https://reg2.io'],
       })
-    ).rejects.toThrow(DATASOURCE_FAILURE);
+    ).rejects.toThrow(EXTERNAL_HOST_ERROR);
   });
   it('hunts registries and passes on error', async () => {
     packagistDatasource.getReleases.mockImplementationOnce(() => {
@@ -173,9 +173,9 @@ describe('datasource/index', () => {
     expect(res).toMatchSnapshot();
     expect(res.releases).toHaveLength(2);
   });
-  it('merges registries and aborts on DatasourceError', async () => {
+  it('merges registries and aborts on ExternalHostError', async () => {
     mavenDatasource.getReleases.mockImplementationOnce(() => {
-      throw new DatasourceError(new Error());
+      throw new ExternalHostError(new Error());
     });
     await expect(
       datasource.getPkgReleases({
@@ -183,7 +183,7 @@ describe('datasource/index', () => {
         depName: 'something',
         registryUrls: ['https://reg1.com', 'https://reg2.io'],
       })
-    ).rejects.toThrow(DATASOURCE_FAILURE);
+    ).rejects.toThrow(EXTERNAL_HOST_ERROR);
   });
   it('merges registries and passes on error', async () => {
     mavenDatasource.getReleases.mockImplementationOnce(() => {
diff --git a/lib/datasource/index.ts b/lib/datasource/index.ts
index 68cd63b951..dba7f32e5f 100644
--- a/lib/datasource/index.ts
+++ b/lib/datasource/index.ts
@@ -1,13 +1,13 @@
 import is from '@sindresorhus/is';
 import _ from 'lodash';
 import { logger } from '../logger';
+import { ExternalHostError } from '../types/error';
 import * as runCache from '../util/cache/run';
 import { clone } from '../util/clone';
 import * as allVersioning from '../versioning';
 import datasources from './api.generated';
 import {
   Datasource,
-  DatasourceError,
   DigestConfig,
   GetPkgReleasesConfig,
   GetReleasesConfig,
@@ -65,7 +65,7 @@ async function huntRegistries(
         break;
       }
     } catch (err) {
-      if (err instanceof DatasourceError) {
+      if (err instanceof ExternalHostError) {
         throw err;
       }
       // We'll always save the last-thrown error
@@ -100,7 +100,7 @@ async function mergeRegistries(
         combinedRes = res;
       }
     } catch (err) {
-      if (err instanceof DatasourceError) {
+      if (err instanceof ExternalHostError) {
         throw err;
       }
       // We'll always save the last-thrown error
@@ -221,8 +221,8 @@ export async function getPkgReleases(
       })
     );
   } catch (e) /* istanbul ignore next */ {
-    if (e instanceof DatasourceError) {
-      e.datasource = config.datasource;
+    if (e instanceof ExternalHostError) {
+      e.hostType = config.datasource;
       e.lookupName = lookupName;
     }
     throw e;
diff --git a/lib/datasource/maven/index.spec.ts b/lib/datasource/maven/index.spec.ts
index bbe3d11b6d..4349d4ba29 100644
--- a/lib/datasource/maven/index.spec.ts
+++ b/lib/datasource/maven/index.spec.ts
@@ -2,7 +2,7 @@ import fs from 'fs';
 import { resolve } from 'path';
 import nock from 'nock';
 import { getPkgReleases } from '..';
-import { DATASOURCE_FAILURE } from '../../constants/error-messages';
+import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages';
 import * as hostRules from '../../util/host-rules';
 import * as mavenVersioning from '../../versioning/maven';
 import { id as datasource } from '.';
@@ -182,7 +182,7 @@ describe('datasource/maven', () => {
       expect(releases.releases).toEqual(generateReleases(MYSQL_VERSIONS));
     });
 
-    it('should throw registry-failure if default maven repo fails', async () => {
+    it('should throw external-host-error if default maven repo fails', async () => {
       nock('https://repo.maven.apache.org')
         .get('/maven2/org/artifact/maven-metadata.xml')
         .times(4)
@@ -195,7 +195,7 @@ describe('datasource/maven', () => {
           depName: 'org:artifact',
           registryUrls: ['https://repo.maven.apache.org/maven2/'],
         })
-      ).rejects.toThrow(Error(DATASOURCE_FAILURE));
+      ).rejects.toThrow(EXTERNAL_HOST_ERROR);
     });
 
     it('should return all versions of a specific library if a repository fails because invalid protocol', async () => {
diff --git a/lib/datasource/maven/util.ts b/lib/datasource/maven/util.ts
index 4639aea1c3..bb03867e9a 100644
--- a/lib/datasource/maven/util.ts
+++ b/lib/datasource/maven/util.ts
@@ -1,7 +1,7 @@
 import url from 'url';
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import { Http } from '../../util/http';
-import { DatasourceError } from '../common';
 
 import { MAVEN_REPO, id } from './common';
 
@@ -77,7 +77,7 @@ export async function downloadHttpProtocol(
     } else if (isTemporalError(err)) {
       logger.debug({ failedUrl, err }, 'Temporary error');
       if (isMavenCentral(pkgUrl)) {
-        throw new DatasourceError(err);
+        throw new ExternalHostError(err);
       }
     } else if (isConnectionError(err)) {
       // istanbul ignore next
diff --git a/lib/datasource/npm/get.spec.ts b/lib/datasource/npm/get.spec.ts
index 2d394b010d..ec305f2579 100644
--- a/lib/datasource/npm/get.spec.ts
+++ b/lib/datasource/npm/get.spec.ts
@@ -1,6 +1,6 @@
 import * as httpMock from '../../../test/httpMock';
 import { getName } from '../../../test/util';
-import { DatasourceError } from '../common';
+import { ExternalHostError } from '../../types/error';
 import { getDependency, resetMemCache } from './get';
 import { setNpmrc } from './npmrc';
 
@@ -150,7 +150,7 @@ describe(getName(__filename), () => {
       .get('/npm-parse-error')
       .reply(200, 'not-a-json');
     await expect(getDependency('npm-parse-error', 0)).rejects.toThrow(
-      DatasourceError
+      ExternalHostError
     );
 
     httpMock
diff --git a/lib/datasource/npm/get.ts b/lib/datasource/npm/get.ts
index d2f0e2ab36..dee919732a 100644
--- a/lib/datasource/npm/get.ts
+++ b/lib/datasource/npm/get.ts
@@ -6,11 +6,12 @@ import moment from 'moment';
 import registryAuthToken from 'registry-auth-token';
 import getRegistryUrl from 'registry-auth-token/registry-url';
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import * as globalCache from '../../util/cache/global';
 import { find } from '../../util/host-rules';
 import { Http, HttpOptions } from '../../util/http';
 import { maskToken } from '../../util/mask';
-import { DatasourceError, Release, ReleaseResult } from '../common';
+import { Release, ReleaseResult } from '../common';
 import { id } from './common';
 import { getNpmrc } from './npmrc';
 
@@ -280,7 +281,7 @@ export async function getDependency(
       if (err.name === 'ParseError' && err.body) {
         err.body = 'err.body deleted by Renovate';
       }
-      throw new DatasourceError(err);
+      throw new ExternalHostError(err);
     }
     return null;
   }
diff --git a/lib/datasource/npm/index.spec.ts b/lib/datasource/npm/index.spec.ts
index f2106aacbc..00dfde3e16 100644
--- a/lib/datasource/npm/index.spec.ts
+++ b/lib/datasource/npm/index.spec.ts
@@ -3,7 +3,7 @@ import nock from 'nock';
 import _registryAuthToken from 'registry-auth-token';
 import { getPkgReleases } from '..';
 import { getName } from '../../../test/util';
-import { DATASOURCE_FAILURE } from '../../constants/error-messages';
+import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages';
 import * as hostRules from '../../util/host-rules';
 import { id as datasource, getNpmrc, resetCache, setNpmrc } from '.';
 
@@ -204,13 +204,13 @@ describe(getName(__filename), () => {
     nock('https://registry.npmjs.org').get('/foobar').reply(503);
     await expect(
       getPkgReleases({ datasource, depName: 'foobar' })
-    ).rejects.toThrow(Error(DATASOURCE_FAILURE));
+    ).rejects.toThrow(EXTERNAL_HOST_ERROR);
   });
   it('should throw error for 408', async () => {
     nock('https://registry.npmjs.org').get('/foobar').reply(408);
     await expect(
       getPkgReleases({ datasource, depName: 'foobar' })
-    ).rejects.toThrow(Error(DATASOURCE_FAILURE));
+    ).rejects.toThrow(EXTERNAL_HOST_ERROR);
   });
   it('should throw error for others', async () => {
     nock('https://registry.npmjs.org').get('/foobar').reply(451);
diff --git a/lib/datasource/packagist/index.ts b/lib/datasource/packagist/index.ts
index 77946c2bb4..7fce259e40 100644
--- a/lib/datasource/packagist/index.ts
+++ b/lib/datasource/packagist/index.ts
@@ -2,11 +2,12 @@ import URL from 'url';
 
 import pAll from 'p-all';
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import * as globalCache from '../../util/cache/global';
 import * as runCache from '../../util/cache/run';
 import * as hostRules from '../../util/host-rules';
 import { Http, HttpOptions } from '../../util/http';
-import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common';
+import { GetReleasesConfig, ReleaseResult } from '../common';
 
 export const id = 'packagist';
 export const defaultRegistryUrls = ['https://packagist.org'];
@@ -313,10 +314,10 @@ async function packageLookup(
     }
     if (err.host === 'packagist.org') {
       if (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT') {
-        throw new DatasourceError(err);
+        throw new ExternalHostError(err);
       }
       if (err.statusCode && err.statusCode >= 500 && err.statusCode < 600) {
-        throw new DatasourceError(err);
+        throw new ExternalHostError(err);
       }
     }
     logger.warn({ err, name }, 'packagist registry failure: Unknown error');
diff --git a/lib/datasource/pod/index.spec.ts b/lib/datasource/pod/index.spec.ts
index 7a637e56be..780f51d935 100644
--- a/lib/datasource/pod/index.spec.ts
+++ b/lib/datasource/pod/index.spec.ts
@@ -1,5 +1,6 @@
 import { getPkgReleases } from '..';
 import * as httpMock from '../../../test/httpMock';
+import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages';
 import * as rubyVersioning from '../../versioning/ruby';
 import * as pod from '.';
 
@@ -63,7 +64,7 @@ describe('datasource/cocoapods', () => {
         .scope(cocoapodsHost)
         .get('/all_pods_versions_a_c_b.txt')
         .reply(429);
-      await expect(getPkgReleases(config)).rejects.toThrow('registry-failure');
+      await expect(getPkgReleases(config)).rejects.toThrow(EXTERNAL_HOST_ERROR);
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('returns null for unknown error', async () => {
diff --git a/lib/datasource/pod/index.ts b/lib/datasource/pod/index.ts
index 24b0361f87..391719f731 100644
--- a/lib/datasource/pod/index.ts
+++ b/lib/datasource/pod/index.ts
@@ -1,5 +1,6 @@
 import crypto from 'crypto';
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import * as globalCache from '../../util/cache/global';
 import { Http } from '../../util/http';
 import { GithubHttp } from '../../util/http/github';
@@ -44,7 +45,7 @@ function handleError(lookupName: string, err: Error): void {
     (err.statusCode >= 500 && err.statusCode < 600)
   ) {
     logger.warn({ lookupName, err }, `CocoaPods registry failure`);
-    throw new Error('registry-failure');
+    throw new ExternalHostError(err);
   }
 
   if (err.statusCode === 401) {
diff --git a/lib/datasource/repology/index.spec.ts b/lib/datasource/repology/index.spec.ts
index 56b34cdcab..bd08b9d04e 100644
--- a/lib/datasource/repology/index.spec.ts
+++ b/lib/datasource/repology/index.spec.ts
@@ -2,7 +2,7 @@ import fs from 'fs';
 import { getPkgReleases } from '..';
 import * as httpMock from '../../../test/httpMock';
 import { getName } from '../../../test/util';
-import { DATASOURCE_FAILURE } from '../../constants/error-messages';
+import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages';
 import { id as versioning } from '../../versioning/loose';
 import { RepologyPackage, id as datasource } from '.';
 
@@ -127,7 +127,7 @@ describe(getName(__filename), () => {
           versioning,
           depName: 'debian_stable/nginx',
         })
-      ).rejects.toThrow(DATASOURCE_FAILURE);
+      ).rejects.toThrow(EXTERNAL_HOST_ERROR);
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
 
@@ -140,7 +140,7 @@ describe(getName(__filename), () => {
           versioning,
           depName: 'debian_stable/nginx',
         })
-      ).rejects.toThrow(DATASOURCE_FAILURE);
+      ).rejects.toThrow(EXTERNAL_HOST_ERROR);
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
 
@@ -151,7 +151,7 @@ describe(getName(__filename), () => {
           versioning,
           depName: 'invalid-lookup-name',
         })
-      ).rejects.toThrow(DATASOURCE_FAILURE);
+      ).rejects.toThrow(EXTERNAL_HOST_ERROR);
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
 
diff --git a/lib/datasource/repology/index.ts b/lib/datasource/repology/index.ts
index e45c10f021..7af17eb775 100644
--- a/lib/datasource/repology/index.ts
+++ b/lib/datasource/repology/index.ts
@@ -1,8 +1,9 @@
 import { URLSearchParams } from 'url';
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import * as globalCache from '../../util/cache/global';
 import { Http } from '../../util/http';
-import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common';
+import { GetReleasesConfig, ReleaseResult } from '../common';
 
 export const id = 'repology';
 
@@ -117,7 +118,7 @@ export async function getReleases({
   // Ensure lookup name contains both repository and package
   const [repoName, pkgName] = lookupName.split('/', 2);
   if (!repoName || !pkgName) {
-    throw new DatasourceError(
+    throw new ExternalHostError(
       new Error(
         'Repology lookup name must contain repository and package separated by slash (<repo>/<pkg>)'
       )
@@ -142,6 +143,6 @@ export async function getReleases({
       { lookupName, err },
       'Repology lookup failed with unexpected error'
     );
-    throw new DatasourceError(err);
+    throw new ExternalHostError(err);
   }
 }
diff --git a/lib/datasource/ruby-version/index.ts b/lib/datasource/ruby-version/index.ts
index c3315f04e1..d85ee6cec9 100644
--- a/lib/datasource/ruby-version/index.ts
+++ b/lib/datasource/ruby-version/index.ts
@@ -1,9 +1,10 @@
 import { parse } from 'node-html-parser';
 
+import { ExternalHostError } from '../../types/error';
 import * as globalCache from '../../util/cache/global';
 import { Http } from '../../util/http';
 import { isVersion } from '../../versioning/ruby';
-import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common';
+import { GetReleasesConfig, ReleaseResult } from '../common';
 
 export const id = 'ruby-version';
 
@@ -51,6 +52,6 @@ export async function getReleases(
     await globalCache.set(cacheNamespace, 'all', res, 15);
     return res;
   } catch (err) {
-    throw new DatasourceError(err);
+    throw new ExternalHostError(err);
   }
 }
diff --git a/lib/datasource/rubygems/get-rubygems-org.ts b/lib/datasource/rubygems/get-rubygems-org.ts
index 3eb01a4ed9..f300557322 100644
--- a/lib/datasource/rubygems/get-rubygems-org.ts
+++ b/lib/datasource/rubygems/get-rubygems-org.ts
@@ -1,6 +1,7 @@
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import { Http } from '../../util/http';
-import { DatasourceError, ReleaseResult } from '../common';
+import { ReleaseResult } from '../common';
 import { id } from './common';
 
 const http = new Http(id);
@@ -38,7 +39,7 @@ async function updateRubyGemsVersions(): Promise<void> {
     if (err.statusCode !== 416) {
       contentLength = 0;
       packageReleases = Object.create(null); // Because we might need a "constructor" key
-      throw new DatasourceError(
+      throw new ExternalHostError(
         new Error('Rubygems fetch error - need to reset cache')
       );
     }
diff --git a/lib/datasource/terraform-module/index.ts b/lib/datasource/terraform-module/index.ts
index da0bce26ad..4cac9a7d80 100644
--- a/lib/datasource/terraform-module/index.ts
+++ b/lib/datasource/terraform-module/index.ts
@@ -1,7 +1,8 @@
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import * as globalCache from '../../util/cache/global';
 import { Http } from '../../util/http';
-import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common';
+import { GetReleasesConfig, ReleaseResult } from '../common';
 
 export const id = 'terraform-module';
 export const defaultRegistryUrls = ['https://registry.terraform.io'];
@@ -113,7 +114,7 @@ export async function getReleases({
     const failureCodes = ['EAI_AGAIN'];
     // istanbul ignore if
     if (failureCodes.includes(err.code)) {
-      throw new DatasourceError(err);
+      throw new ExternalHostError(err);
     }
     logger.warn(
       { err, lookupName },
diff --git a/lib/manager/gradle/index.ts b/lib/manager/gradle/index.ts
index 0d1ea33e15..2b1d02d87b 100644
--- a/lib/manager/gradle/index.ts
+++ b/lib/manager/gradle/index.ts
@@ -3,9 +3,9 @@ import * as os from 'os';
 import * as fs from 'fs-extra';
 import upath from 'upath';
 import { LANGUAGE_JAVA } from '../../constants/languages';
-import { DatasourceError } from '../../datasource';
 import * as datasourceMaven from '../../datasource/maven';
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import { ExecOptions, exec } from '../../util/exec';
 import { BinarySource } from '../../util/exec/common';
 import { readLocalFile } from '../../util/fs';
@@ -106,9 +106,7 @@ export async function executeGradle(
     ({ stdout, stderr } = await exec(cmd, execOptions));
   } catch (err) /* istanbul ignore next */ {
     if (err.code === TIMEOUT_CODE) {
-      const error = new DatasourceError(err);
-      error.datasource = 'gradle';
-      throw error;
+      throw new ExternalHostError(err, 'gradle');
     }
     logger.warn({ errMessage: err.message }, 'Gradle extraction failed');
     return;
diff --git a/lib/manager/npm/post-update/index.ts b/lib/manager/npm/post-update/index.ts
index 7ba4aae6f2..75cf85f9c3 100644
--- a/lib/manager/npm/post-update/index.ts
+++ b/lib/manager/npm/post-update/index.ts
@@ -3,9 +3,10 @@ import fs from 'fs-extra';
 import upath from 'upath';
 // eslint-disable-next-line import/no-unresolved
 import { SYSTEM_INSUFFICIENT_DISK_SPACE } from '../../../constants/error-messages';
-import { DatasourceError } from '../../../datasource/common';
+import { id as npmId } from '../../../datasource/npm';
 import { logger } from '../../../logger';
 import { platform } from '../../../platform';
+import { ExternalHostError } from '../../../types/error';
 import { getChildProcessEnv } from '../../../util/exec/env';
 import { deleteLocalFile } from '../../../util/fs';
 import * as hostRules from '../../../util/host-rules';
@@ -432,7 +433,7 @@ export async function getAdditionalFiles(
             const err = new Error(
               'lock file failed for the dependency being updated - skipping branch creation'
             );
-            throw new DatasourceError(err);
+            throw new ExternalHostError(err, npmId);
           }
         }
       }
@@ -492,10 +493,11 @@ export async function getAdditionalFiles(
               { dependency: upgrade.depName, type: 'yarn' },
               'lock file failed for the dependency being updated - skipping branch creation'
             );
-            throw new DatasourceError(
+            throw new ExternalHostError(
               new Error(
                 'lock file failed for the dependency being updated - skipping branch creation'
-              )
+              ),
+              npmId
             );
           }
           /* eslint-enable no-useless-escape */
@@ -594,10 +596,11 @@ export async function getAdditionalFiles(
               { dependency: upgrade.depName, type: 'pnpm' },
               'lock file failed for the dependency being updated - skipping branch creation'
             );
-            throw new DatasourceError(
+            throw new ExternalHostError(
               Error(
                 'lock file failed for the dependency being updated - skipping branch creation'
-              )
+              ),
+              npmId
             );
           }
         }
@@ -672,10 +675,11 @@ export async function getAdditionalFiles(
             { dependency: upgrade.depName, type: 'yarn' },
             'lock file failed for the dependency being updated - skipping branch creation'
           );
-          throw new DatasourceError(
+          throw new ExternalHostError(
             Error(
               'lock file failed for the dependency being updated - skipping branch creation'
-            )
+            ),
+            npmId
           );
         }
         /* eslint-enable no-useless-escape */
@@ -688,10 +692,11 @@ export async function getAdditionalFiles(
             { dependency: upgrade.depName, type: 'npm' },
             'lock file failed for the dependency being updated - skipping branch creation'
           );
-          throw new DatasourceError(
+          throw new ExternalHostError(
             Error(
               'lock file failed for the dependency being updated - skipping branch creation'
-            )
+            ),
+            npmId
           );
         }
       }
diff --git a/lib/manager/npm/post-update/yarn.ts b/lib/manager/npm/post-update/yarn.ts
index 81d2d6e8d7..1dcc798c0b 100644
--- a/lib/manager/npm/post-update/yarn.ts
+++ b/lib/manager/npm/post-update/yarn.ts
@@ -4,8 +4,9 @@ import { validRange } from 'semver';
 import { quote } from 'shlex';
 import { join } from 'upath';
 import { SYSTEM_INSUFFICIENT_DISK_SPACE } from '../../../constants/error-messages';
-import { DatasourceError } from '../../../datasource';
+import { id as npmId } from '../../../datasource/npm';
 import { logger } from '../../../logger';
+import { ExternalHostError } from '../../../types/error';
 import { ExecOptions, exec } from '../../../util/exec';
 import { PostUpdateConfig, Upgrade } from '../../common';
 import { getNodeConstraint } from './node-version';
@@ -151,7 +152,7 @@ export async function generateLockFile(
         err.stderr.includes('getaddrinfo ENOTFOUND registry.yarnpkg.com') ||
         err.stderr.includes('getaddrinfo ENOTFOUND registry.npmjs.org')
       ) {
-        throw new DatasourceError(err);
+        throw new ExternalHostError(err, npmId);
       }
     }
     return { error: true, stderr: err.stderr };
diff --git a/lib/platform/git/storage.ts b/lib/platform/git/storage.ts
index 1784abbfc1..b857ed7f36 100644
--- a/lib/platform/git/storage.ts
+++ b/lib/platform/git/storage.ts
@@ -4,13 +4,13 @@ import fs from 'fs-extra';
 import Git from 'simple-git/promise';
 import {
   CONFIG_VALIDATION,
-  PLATFORM_FAILURE,
   REPOSITORY_CHANGED,
   REPOSITORY_EMPTY,
   REPOSITORY_TEMPORARY_ERROR,
   SYSTEM_INSUFFICIENT_DISK_SPACE,
 } from '../../constants/error-messages';
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import * as limits from '../../workers/global/limits';
 import { CommitFilesConfig } from '../common';
 import { writePrivateKey } from './private-key';
@@ -54,7 +54,7 @@ function checkForPlatformFailure(err: Error): void {
   ];
   for (const errorStr of platformFailureStrings) {
     if (err.message.includes(errorStr)) {
-      throw new Error(PLATFORM_FAILURE);
+      throw new ExternalHostError(err, 'git');
     }
   }
 }
@@ -176,7 +176,7 @@ export class Storage {
         if (err.message?.includes('write error: No space left on device')) {
           throw new Error(SYSTEM_INSUFFICIENT_DISK_SPACE);
         }
-        throw new Error(PLATFORM_FAILURE);
+        throw new ExternalHostError(err, 'git');
       }
       const durationMs = Math.round(Date.now() - cloneStart);
       logger.debug({ durationMs }, 'git clone completed');
diff --git a/lib/platform/github/index.ts b/lib/platform/github/index.ts
index 62f77f00ab..644643b558 100644
--- a/lib/platform/github/index.ts
+++ b/lib/platform/github/index.ts
@@ -3,7 +3,6 @@ import is from '@sindresorhus/is';
 import delay from 'delay';
 import { configFileNames } from '../../config/app-strings';
 import {
-  PLATFORM_FAILURE,
   PLATFORM_INTEGRATION_UNAUTHORIZED,
   REPOSITORY_ACCESS_FORBIDDEN,
   REPOSITORY_ARCHIVED,
@@ -24,6 +23,7 @@ import {
 } from '../../constants/pull-requests';
 import { logger } from '../../logger';
 import { BranchStatus } from '../../types';
+import { ExternalHostError } from '../../types/error';
 import * as hostRules from '../../util/host-rules';
 import * as githubHttp from '../../util/http/github';
 import { sanitize } from '../../util/sanitize';
@@ -397,7 +397,7 @@ export async function initRepo({
           }
         );
       } catch (err) /* istanbul ignore next */ {
-        if (err.message === PLATFORM_FAILURE) {
+        if (err instanceof ExternalHostError) {
           throw err;
         }
         if (
@@ -965,7 +965,7 @@ export async function getPrList(): Promise<Pr[]> {
       );
     } catch (err) /* istanbul ignore next */ {
       logger.debug({ err }, 'getPrList err');
-      throw new Error('platform-failure');
+      throw new ExternalHostError(err, PLATFORM_TYPE_GITHUB);
     }
     config.prList = res.body.map((pr) => ({
       number: pr.number,
@@ -1094,7 +1094,7 @@ export async function getBranchStatus(
         logger.debug({ result: checkRunsRaw }, 'No check runs found');
       }
     } catch (err) /* istanbul ignore next */ {
-      if (err.message === PLATFORM_FAILURE) {
+      if (err instanceof ExternalHostError) {
         throw err;
       }
       if (
@@ -1506,7 +1506,7 @@ async function getComments(issueNo: number): Promise<Comment[]> {
   } catch (err) /* istanbul ignore next */ {
     if (err.statusCode === 404) {
       logger.debug('404 respose when retrieving comments');
-      throw new Error(PLATFORM_FAILURE);
+      throw new ExternalHostError(err, PLATFORM_TYPE_GITHUB);
     }
     throw err;
   }
@@ -1559,7 +1559,7 @@ export async function ensureComment({
     }
     return true;
   } catch (err) /* istanbul ignore next */ {
-    if (err.message === PLATFORM_FAILURE) {
+    if (err instanceof ExternalHostError) {
       throw err;
     }
     if (err.body?.message?.includes('is locked')) {
@@ -1690,7 +1690,7 @@ export async function updatePr(
     );
     logger.debug({ pr: prNo }, 'PR updated');
   } catch (err) /* istanbul ignore next */ {
-    if (err.message === PLATFORM_FAILURE) {
+    if (err instanceof ExternalHostError) {
       throw err;
     }
     logger.warn({ err }, 'Error updating PR');
diff --git a/lib/types/error.ts b/lib/types/error.ts
new file mode 100644
index 0000000000..b6e67592d0
--- /dev/null
+++ b/lib/types/error.ts
@@ -0,0 +1,19 @@
+import { EXTERNAL_HOST_ERROR } from '../constants/error-messages';
+
+export class ExternalHostError extends Error {
+  hostType: string;
+
+  err: Error;
+
+  lookupName?: string;
+
+  reason?: string;
+
+  constructor(err: Error, hostType?: string) {
+    super(EXTERNAL_HOST_ERROR);
+    // Set the prototype explicitly: https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
+    Object.setPrototypeOf(this, ExternalHostError.prototype);
+    this.hostType = hostType;
+    this.err = err;
+  }
+}
diff --git a/lib/util/http/github.spec.ts b/lib/util/http/github.spec.ts
index 444d9ef06c..fb4e5b38aa 100644
--- a/lib/util/http/github.spec.ts
+++ b/lib/util/http/github.spec.ts
@@ -2,8 +2,8 @@ import delay from 'delay';
 import * as httpMock from '../../../test/httpMock';
 import { getName } from '../../../test/util';
 import {
+  EXTERNAL_HOST_ERROR,
   PLATFORM_BAD_CREDENTIALS,
-  PLATFORM_FAILURE,
   PLATFORM_INTEGRATION_UNAUTHORIZED,
   PLATFORM_RATE_LIMIT_EXCEEDED,
   REPOSITORY_CHANGED,
@@ -126,7 +126,7 @@ describe(getName(__filename), () => {
         },
       });
       expect(e).toBeDefined();
-      expect(e.message).toEqual(PLATFORM_FAILURE);
+      expect(e.message).toEqual(EXTERNAL_HOST_ERROR);
     });
     it('should throw platform failure for ENOTFOUND, ETIMEDOUT or EAI_AGAIN', () => {
       const codes = ['ENOTFOUND', 'ETIMEDOUT', 'EAI_AGAIN'];
@@ -137,7 +137,7 @@ describe(getName(__filename), () => {
           code,
         });
         expect(e).toBeDefined();
-        expect(e.message).toEqual(PLATFORM_FAILURE);
+        expect(e.message).toEqual(EXTERNAL_HOST_ERROR);
       }
     });
     it('should throw platform failure for 500', () => {
@@ -146,14 +146,14 @@ describe(getName(__filename), () => {
         message: 'Internal Server Error',
       });
       expect(e).toBeDefined();
-      expect(e.message).toEqual(PLATFORM_FAILURE);
+      expect(e.message).toEqual(EXTERNAL_HOST_ERROR);
     });
     it('should throw platform failure ParseError', () => {
       const e = getError({
         name: 'ParseError',
       });
       expect(e).toBeDefined();
-      expect(e.message).toEqual(PLATFORM_FAILURE);
+      expect(e.message).toEqual(EXTERNAL_HOST_ERROR);
     });
     it('should throw for unauthorized integration', () => {
       const e = getError({
@@ -200,7 +200,7 @@ describe(getName(__filename), () => {
       };
       const e = getError(gotErr);
       expect(e).toBeDefined();
-      expect(e.message).toEqual(PLATFORM_FAILURE);
+      expect(e.message).toEqual(EXTERNAL_HOST_ERROR);
     });
     it('should throw original error when failed to add reviewers', () => {
       const gotErr = {
diff --git a/lib/util/http/github.ts b/lib/util/http/github.ts
index 76c7d9d76d..c2f6dd1b65 100644
--- a/lib/util/http/github.ts
+++ b/lib/util/http/github.ts
@@ -4,13 +4,13 @@ import pAll from 'p-all';
 import parseLinkHeader from 'parse-link-header';
 import {
   PLATFORM_BAD_CREDENTIALS,
-  PLATFORM_FAILURE,
   PLATFORM_INTEGRATION_UNAUTHORIZED,
   PLATFORM_RATE_LIMIT_EXCEEDED,
   REPOSITORY_CHANGED,
 } from '../../constants/error-messages';
 import { PLATFORM_TYPE_GITHUB } from '../../constants/platforms';
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import { maskToken } from '../mask';
 import { Http, HttpPostOptions, HttpResponse, InternalHttpOptions } from '.';
 
@@ -61,15 +61,15 @@ export function handleGotError(
       err.code === 'EAI_AGAIN')
   ) {
     logger.debug({ err }, 'GitHub failure: RequestError');
-    throw new Error(PLATFORM_FAILURE);
+    throw new ExternalHostError(err, PLATFORM_TYPE_GITHUB);
   }
   if (err.name === 'ParseError') {
     logger.debug({ err }, '');
-    throw new Error(PLATFORM_FAILURE);
+    throw new ExternalHostError(err, PLATFORM_TYPE_GITHUB);
   }
   if (err.statusCode >= 500 && err.statusCode < 600) {
     logger.debug({ err }, 'GitHub failure: 5xx');
-    throw new Error(PLATFORM_FAILURE);
+    throw new ExternalHostError(err, PLATFORM_TYPE_GITHUB);
   }
   if (
     err.statusCode === 403 &&
@@ -106,7 +106,7 @@ export function handleGotError(
       'GitHub failure: Bad credentials'
     );
     if (rateLimit === '60') {
-      throw new Error(PLATFORM_FAILURE);
+      throw new ExternalHostError(err, PLATFORM_TYPE_GITHUB);
     }
     throw new Error(PLATFORM_BAD_CREDENTIALS);
   }
@@ -123,7 +123,7 @@ export function handleGotError(
       throw new Error(REPOSITORY_CHANGED);
     }
     logger.debug({ err }, '422 Error thrown from GitHub');
-    throw new Error(PLATFORM_FAILURE);
+    throw new ExternalHostError(err, PLATFORM_TYPE_GITHUB);
   }
   if (err.statusCode === 404) {
     logger.debug({ url: err.url }, 'GitHub 404');
diff --git a/lib/util/http/gitlab.ts b/lib/util/http/gitlab.ts
index 9a5d0f1605..5e759d3339 100644
--- a/lib/util/http/gitlab.ts
+++ b/lib/util/http/gitlab.ts
@@ -1,8 +1,8 @@
 import { URL } from 'url';
 import parseLinkHeader from 'parse-link-header';
-import { PLATFORM_FAILURE } from '../../constants/error-messages';
 import { PLATFORM_TYPE_GITLAB } from '../../constants/platforms';
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import { Http, HttpResponse, InternalHttpOptions } from '.';
 
 let baseUrl = 'https://gitlab.com/api/v4/';
@@ -63,7 +63,7 @@ export class GitlabHttp extends Http<GitlabHttpOptions, GitlabHttpOptions> {
         err.statusCode === 429 ||
         (err.statusCode >= 500 && err.statusCode < 600)
       ) {
-        throw new Error(PLATFORM_FAILURE);
+        throw new ExternalHostError(err, PLATFORM_TYPE_GITLAB);
       }
       const platformFailureCodes = [
         'EAI_AGAIN',
@@ -72,10 +72,10 @@ export class GitlabHttp extends Http<GitlabHttpOptions, GitlabHttpOptions> {
         'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
       ];
       if (platformFailureCodes.includes(err.code)) {
-        throw new Error(PLATFORM_FAILURE);
+        throw new ExternalHostError(err, PLATFORM_TYPE_GITLAB);
       }
       if (err.name === 'ParseError') {
-        throw new Error(PLATFORM_FAILURE);
+        throw new ExternalHostError(err, PLATFORM_TYPE_GITLAB);
       }
       throw err;
     }
diff --git a/lib/workers/branch/index.ts b/lib/workers/branch/index.ts
index 31963c5771..a646d6dc70 100644
--- a/lib/workers/branch/index.ts
+++ b/lib/workers/branch/index.ts
@@ -4,11 +4,9 @@ import { DateTime } from 'luxon';
 import minimatch from 'minimatch';
 import { RenovateConfig } from '../../config';
 import {
-  DATASOURCE_FAILURE,
   MANAGER_LOCKFILE_ERROR,
   PLATFORM_AUTHENTICATION_ERROR,
   PLATFORM_BAD_CREDENTIALS,
-  PLATFORM_FAILURE,
   PLATFORM_INTEGRATION_UNAUTHORIZED,
   PLATFORM_RATE_LIMIT_EXCEEDED,
   REPOSITORY_CHANGED,
@@ -24,6 +22,7 @@ import { logger } from '../../logger';
 import { getAdditionalFiles } from '../../manager/npm/post-update';
 import { platform } from '../../platform';
 import { BranchStatus } from '../../types';
+import { ExternalHostError } from '../../types/error';
 import { emojify } from '../../util/emoji';
 import { exec } from '../../util/exec';
 import { readLocalFile, writeLocalFile } from '../../util/fs';
@@ -541,10 +540,7 @@ export async function processBranch(
       err.message.includes('fatal: Authentication failed')
     ) {
       throw new Error(PLATFORM_AUTHENTICATION_ERROR);
-    } else if (
-      err.message !== DATASOURCE_FAILURE &&
-      err.message !== PLATFORM_FAILURE
-    ) {
+    } else if (!(err instanceof ExternalHostError)) {
       logger.error({ err }, `Error updating branch: ${err.message}`);
     }
     // Don't throw here - we don't want to stop the other renovations
@@ -655,11 +651,8 @@ export async function processBranch(
     }
   } catch (err) /* istanbul ignore next */ {
     if (
-      [
-        PLATFORM_RATE_LIMIT_EXCEEDED,
-        PLATFORM_FAILURE,
-        REPOSITORY_CHANGED,
-      ].includes(err.message)
+      err instanceof ExternalHostError ||
+      [PLATFORM_RATE_LIMIT_EXCEEDED, REPOSITORY_CHANGED].includes(err.message)
     ) {
       logger.debug('Passing PR error up');
       throw err;
diff --git a/lib/workers/pr/index.ts b/lib/workers/pr/index.ts
index 9fc7e586e9..17c2bcdca2 100644
--- a/lib/workers/pr/index.ts
+++ b/lib/workers/pr/index.ts
@@ -2,7 +2,6 @@ import sampleSize from 'lodash/sampleSize';
 import uniq from 'lodash/uniq';
 import { RenovateConfig } from '../../config/common';
 import {
-  PLATFORM_FAILURE,
   PLATFORM_INTEGRATION_UNAUTHORIZED,
   PLATFORM_RATE_LIMIT_EXCEEDED,
   REPOSITORY_CHANGED,
@@ -10,6 +9,7 @@ import {
 import { logger } from '../../logger';
 import { PlatformPrOptions, Pr, platform } from '../../platform';
 import { BranchStatus } from '../../types';
+import { ExternalHostError } from '../../types/error';
 import { BranchConfig, PrResult } from '../common';
 import { getPrBody } from './body';
 import { ChangeLogError } from './changelog';
@@ -427,9 +427,9 @@ export async function ensurePr(
   } catch (err) {
     // istanbul ignore if
     if (
+      err instanceof ExternalHostError ||
       err.message === REPOSITORY_CHANGED ||
       err.message === PLATFORM_RATE_LIMIT_EXCEEDED ||
-      err.message === PLATFORM_FAILURE ||
       err.message === PLATFORM_INTEGRATION_UNAUTHORIZED
     ) {
       logger.debug('Passing error up');
diff --git a/lib/workers/repository/error.spec.ts b/lib/workers/repository/error.spec.ts
index f0b10e9548..5fce970db9 100644
--- a/lib/workers/repository/error.spec.ts
+++ b/lib/workers/repository/error.spec.ts
@@ -1,12 +1,11 @@
 import { RenovateConfig, getConfig } from '../../../test/util';
 import {
   CONFIG_VALIDATION,
-  DATASOURCE_FAILURE,
+  EXTERNAL_HOST_ERROR,
   MANAGER_LOCKFILE_ERROR,
   MANAGER_NO_PACKAGE_FILES,
   PLATFORM_AUTHENTICATION_ERROR,
   PLATFORM_BAD_CREDENTIALS,
-  PLATFORM_FAILURE,
   PLATFORM_INTEGRATION_UNAUTHORIZED,
   PLATFORM_RATE_LIMIT_EXCEEDED,
   REPOSITORY_ACCESS_FORBIDDEN,
@@ -27,7 +26,7 @@ import {
   SYSTEM_INSUFFICIENT_MEMORY,
   UNKNOWN_ERROR,
 } from '../../constants/error-messages';
-import { DatasourceError } from '../../datasource/common';
+import { ExternalHostError } from '../../types/error';
 import handleError from './error';
 
 jest.mock('./error-config');
@@ -48,7 +47,6 @@ describe('workers/repository/error', () => {
       REPOSITORY_FORKED,
       MANAGER_NO_PACKAGE_FILES,
       CONFIG_VALIDATION,
-      DATASOURCE_FAILURE,
       REPOSITORY_ARCHIVED,
       REPOSITORY_MIRRORED,
       REPOSITORY_RENAMED,
@@ -60,7 +58,6 @@ describe('workers/repository/error', () => {
       MANAGER_LOCKFILE_ERROR,
       SYSTEM_INSUFFICIENT_DISK_SPACE,
       SYSTEM_INSUFFICIENT_MEMORY,
-      PLATFORM_FAILURE,
       REPOSITORY_NO_VULNERABILITY,
       REPOSITORY_CANNOT_FORK,
       PLATFORM_INTEGRATION_UNAUTHORIZED,
@@ -73,23 +70,26 @@ describe('workers/repository/error', () => {
         expect(res).toEqual(err);
       });
     });
-    it(`handles DatasourceError`, async () => {
-      const res = await handleError(config, new DatasourceError(new Error()));
-      expect(res).toEqual(DATASOURCE_FAILURE);
+    it(`handles ExternalHostError`, async () => {
+      const res = await handleError(
+        config,
+        new ExternalHostError(new Error(), 'some-host-type')
+      );
+      expect(res).toEqual(EXTERNAL_HOST_ERROR);
     });
     it('rewrites git 5xx error', async () => {
       const gitError = new Error(
         "fatal: unable to access 'https://**redacted**@gitlab.com/learnox/learnox.git/': The requested URL returned error: 500\n"
       );
       const res = await handleError(config, gitError);
-      expect(res).toEqual(PLATFORM_FAILURE);
+      expect(res).toEqual(EXTERNAL_HOST_ERROR);
     });
     it('rewrites git remote error', async () => {
       const gitError = new Error(
         'fatal: remote error: access denied or repository not exported: /b/nw/bd/27/47/159945428/108610112.git\n'
       );
       const res = await handleError(config, gitError);
-      expect(res).toEqual(PLATFORM_FAILURE);
+      expect(res).toEqual(EXTERNAL_HOST_ERROR);
     });
     it('handles unknown error', async () => {
       const res = await handleError(config, new Error('abcdefg'));
diff --git a/lib/workers/repository/error.ts b/lib/workers/repository/error.ts
index 48f8e7a911..5ff14d0aea 100644
--- a/lib/workers/repository/error.ts
+++ b/lib/workers/repository/error.ts
@@ -2,12 +2,11 @@ import { RenovateConfig } from '../../config';
 
 import {
   CONFIG_VALIDATION,
-  DATASOURCE_FAILURE,
+  EXTERNAL_HOST_ERROR,
   MANAGER_LOCKFILE_ERROR,
   MANAGER_NO_PACKAGE_FILES,
   PLATFORM_AUTHENTICATION_ERROR,
   PLATFORM_BAD_CREDENTIALS,
-  PLATFORM_FAILURE,
   PLATFORM_INTEGRATION_UNAUTHORIZED,
   PLATFORM_RATE_LIMIT_EXCEEDED,
   REPOSITORY_ACCESS_FORBIDDEN,
@@ -28,8 +27,8 @@ import {
   SYSTEM_INSUFFICIENT_MEMORY,
   UNKNOWN_ERROR,
 } from '../../constants/error-messages';
-import { DatasourceError } from '../../datasource/common';
 import { logger } from '../../logger';
+import { ExternalHostError } from '../../types/error';
 import { raiseConfigWarningIssue } from './error-config';
 
 export default async function handleError(
@@ -107,22 +106,12 @@ export default async function handleError(
     await raiseConfigWarningIssue(config, err);
     return err.message;
   }
-  if (err instanceof DatasourceError) {
+  if (err instanceof ExternalHostError) {
     logger.warn(
-      { datasource: err.datasource, lookupName: err.lookupName, err: err.err },
-      'Datasource failure'
+      { hostType: err.hostType, lookupName: err.lookupName, err: err.err },
+      'Host error'
     );
-    logger.info('Registry error - skipping');
-    delete config.branchList; // eslint-disable-line no-param-reassign
-    return err.message;
-  }
-  if (err.message === DATASOURCE_FAILURE) {
-    logger.info({ err }, 'Registry error - skipping');
-    delete config.branchList; // eslint-disable-line no-param-reassign
-    return err.message;
-  }
-  if (err.message === PLATFORM_FAILURE) {
-    logger.info('Platform error - skipping');
+    logger.info('External host error causing abort - skipping');
     delete config.branchList; // eslint-disable-line no-param-reassign
     return err.message;
   }
@@ -174,7 +163,7 @@ export default async function handleError(
     logger.warn({ err }, 'Git error - aborting');
     delete config.branchList; // eslint-disable-line no-param-reassign
     // rewrite this error
-    return PLATFORM_FAILURE;
+    return EXTERNAL_HOST_ERROR;
   }
   if (
     err.message.includes('The remote end hung up unexpectedly') ||
@@ -183,7 +172,7 @@ export default async function handleError(
     logger.warn({ err }, 'Git error - aborting');
     delete config.branchList; // eslint-disable-line no-param-reassign
     // rewrite this error
-    return PLATFORM_FAILURE;
+    return EXTERNAL_HOST_ERROR;
   }
   // Swallow this error so that other repositories can be processed
   logger.error({ err }, `Repository has unknown error`);
diff --git a/lib/workers/repository/init/config.ts b/lib/workers/repository/init/config.ts
index 547c2b3933..21ce3d7325 100644
--- a/lib/workers/repository/init/config.ts
+++ b/lib/workers/repository/init/config.ts
@@ -7,13 +7,11 @@ import { configFileNames } from '../../../config/app-strings';
 import { decryptConfig } from '../../../config/decrypt';
 import { migrateAndValidate } from '../../../config/migrate-validate';
 import * as presets from '../../../config/presets';
-import {
-  CONFIG_VALIDATION,
-  PLATFORM_FAILURE,
-} from '../../../constants/error-messages';
+import { CONFIG_VALIDATION } from '../../../constants/error-messages';
 import * as npmApi from '../../../datasource/npm';
 import { logger } from '../../../logger';
 import { platform } from '../../../platform';
+import { ExternalHostError } from '../../../types/error';
 import { readLocalFile } from '../../../util/fs';
 import * as hostRules from '../../../util/host-rules';
 import { flattenPackageRules } from './flatten';
@@ -59,7 +57,10 @@ export async function mergeRenovateConfig(
     // istanbul ignore if
     if (renovateConfig === null) {
       logger.warn('Fetching renovate config returns null');
-      throw new Error(PLATFORM_FAILURE);
+      throw new ExternalHostError(
+        Error('Fetching renovate config returns null'),
+        config.platform
+      );
     }
     // istanbul ignore if
     if (!renovateConfig.length) {
-- 
GitLab