diff --git a/lib/util/promises.spec.ts b/lib/util/promises.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b0c575dab9715adb0ba1d7ff55f25e8ba6104bf2
--- /dev/null
+++ b/lib/util/promises.spec.ts
@@ -0,0 +1,81 @@
+import { ExternalHostError } from '../types/errors/external-host-error';
+import * as p from './promises';
+
+describe('util/promises', () => {
+  describe('all', () => {
+    it('works', async () => {
+      const queue = p.all([
+        () => Promise.resolve(1),
+        () => Promise.resolve(2),
+        () => Promise.resolve(3),
+      ]);
+      await expect(queue).resolves.toEqual([1, 2, 3]);
+    });
+  });
+
+  describe('map', () => {
+    it('works', async () => {
+      const queue = p.map([1, 2, 3], (x) => Promise.resolve(x + 1));
+      await expect(queue).resolves.toEqual([2, 3, 4]);
+    });
+  });
+
+  describe('Error handling', () => {
+    it('throws first ExternalHostError found', async () => {
+      const unknownErr = new Error('fail');
+      const hostErr = new ExternalHostError(unknownErr);
+      let res: Error | string[] | null = null;
+      try {
+        res = await p.all([
+          () => Promise.resolve('ok'),
+          () => Promise.reject(unknownErr),
+          () => Promise.reject(hostErr),
+        ]);
+      } catch (err) {
+        res = err;
+      }
+      expect(res).toBe(hostErr);
+    });
+
+    it('throws first error if error messages are all the same', async () => {
+      const err1 = new Error('some error');
+      const err2 = new Error('some error');
+      const err3 = new Error('some error');
+      let res: Error | string[] | null = null;
+      try {
+        res = await p.all([
+          () => Promise.reject(err1),
+          () => Promise.reject(err2),
+          () => Promise.reject(err3),
+        ]);
+      } catch (err) {
+        res = err;
+      }
+      expect(res).toBe(err1);
+    });
+
+    it('throws aggregate error for different error messages', async () => {
+      await expect(
+        p.map([1, 2, 3], (x) => Promise.reject(new Error(`error ${x}`)))
+      ).rejects.toHaveProperty('name', 'AggregateError');
+    });
+
+    it('re-throws when stopOnError=true', async () => {
+      const unknownErr = new Error('fail');
+      let res: Error | string[] | null = null;
+      try {
+        res = await p.all(
+          [
+            () => Promise.resolve('ok'),
+            () => Promise.resolve('ok'),
+            () => Promise.reject(unknownErr),
+          ],
+          { stopOnError: true }
+        );
+      } catch (err) {
+        res = err;
+      }
+      expect(res).toBe(unknownErr);
+    });
+  });
+});
diff --git a/lib/util/promises.ts b/lib/util/promises.ts
index a0145ced65fe181efbc95b0b2d7fcbb0fea67e43..259369100a122a9ea138b54437653df961b18164 100644
--- a/lib/util/promises.ts
+++ b/lib/util/promises.ts
@@ -1,27 +1,69 @@
+import AggregateError from 'aggregate-error';
 import pAll from 'p-all';
 import pMap from 'p-map';
+import { logger } from '../logger';
+import { ExternalHostError } from '../types/errors/external-host-error';
 
 type PromiseFactory<T> = () => Promise<T>;
 
-export function all<T>(
+function isExternalHostError(err: any): err is ExternalHostError {
+  return err instanceof ExternalHostError;
+}
+
+function handleError(err: any): never {
+  if (!(err instanceof AggregateError)) {
+    throw err;
+  }
+
+  logger.debug({ err }, 'Aggregate error is thrown');
+
+  const errors = [...err];
+
+  const hostError = errors.find(isExternalHostError);
+  if (hostError) {
+    throw hostError;
+  }
+
+  if (
+    errors.length === 1 ||
+    new Set(errors.map(({ message }) => message)).size === 1
+  ) {
+    const [error] = errors;
+    throw error;
+  }
+
+  throw err;
+}
+
+export async function all<T>(
   tasks: PromiseFactory<T>[],
   options?: pAll.Options
 ): Promise<T[]> {
-  return pAll(tasks, {
-    concurrency: 5,
-    ...options,
-    stopOnError: true,
-  });
+  try {
+    const res = await pAll(tasks, {
+      concurrency: 5,
+      stopOnError: false,
+      ...options,
+    });
+    return res;
+  } catch (err) {
+    return handleError(err);
+  }
 }
 
-export function map<Element, NewElement>(
+export async function map<Element, NewElement>(
   input: Iterable<Element>,
   mapper: pMap.Mapper<Element, NewElement>,
   options?: pMap.Options
 ): Promise<NewElement[]> {
-  return pMap(input, mapper, {
-    concurrency: 5,
-    ...options,
-    stopOnError: true,
-  });
+  try {
+    const res = await pMap(input, mapper, {
+      concurrency: 5,
+      stopOnError: false,
+      ...options,
+    });
+    return res;
+  } catch (err) {
+    return handleError(err);
+  }
 }
diff --git a/package.json b/package.json
index a955be6f183b961a08384e3d3da0eb1d82d74a3f..5382f5cd76a65a314394e150418a02a4c88907eb 100644
--- a/package.json
+++ b/package.json
@@ -150,6 +150,7 @@
     "@yarnpkg/core": "3.2.4",
     "@yarnpkg/parsers": "2.5.1",
     "agentkeepalive": "4.2.1",
+    "aggregate-error": "3.1.0",
     "auth-header": "1.0.0",
     "azure-devops-node-api": "11.2.0",
     "bunyan": "1.8.15",
diff --git a/yarn.lock b/yarn.lock
index 895485dde83c3ae58e3833c6676845eaa7ab9eff..711b3ffbc71d5b966b32c5107c32957b47521a72 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3181,7 +3181,7 @@ agentkeepalive@4.2.1, agentkeepalive@^4.2.1:
     depd "^1.1.2"
     humanize-ms "^1.2.1"
 
-aggregate-error@^3.0.0:
+aggregate-error@3.1.0, aggregate-error@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
   integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==