diff --git a/src/Result.ts b/src/Result.ts
new file mode 100644
index 00000000..1e4053af
--- /dev/null
+++ b/src/Result.ts
@@ -0,0 +1,14 @@
+export type Result<V, E> =
+  | {
+      ok: true
+      value: V
+    }
+  | {
+      ok: false
+      error: E
+    }
+
+export const Result = {
+  ok: <V, E>(value: V): Result<V, E> => ({ ok: true, value }),
+  err: <V, E>(error: E): Result<V, E> => ({ ok: false, error }),
+}
diff --git a/src/applyPatches.ts b/src/applyPatches.ts
index 1a50d094..2b342bcb 100644
--- a/src/applyPatches.ts
+++ b/src/applyPatches.ts
@@ -1,10 +1,9 @@
 import chalk from "chalk"
-import { writeFileSync } from "fs"
 import { existsSync } from "fs-extra"
 import { posix } from "path"
 import semver from "semver"
 import { hashFile } from "./hash"
-import { logPatchSequenceError } from "./makePatch"
+import { createPatchSequenceError } from "./makePatch"
 import { PackageDetails, PatchedPackageDetails } from "./PackageDetails"
 import { packageIsDevDependency } from "./packageIsDevDependency"
 import { executeEffects } from "./patch/apply"
@@ -12,6 +11,7 @@ import { readPatch } from "./patch/read"
 import { reversePatch } from "./patch/reverse"
 import { getGroupedPatches } from "./patchFs"
 import { join, relative } from "./path"
+import { Result } from "./Result"
 import {
   clearPatchApplicationState,
   getPatchApplicationState,
@@ -200,12 +200,12 @@ export function applyPatchesForPackage({
         // this patch was applied we can skip it
         appliedPatches.push(unappliedPatches.shift()!)
       } else {
-        console.log(
+        errors.push(
           chalk.red("Error:"),
           `The patches for ${chalk.bold(pathSpecifier)} have changed.`,
           `You should reinstall your node_modules folder to make sure the package is up to date`,
         )
-        process.exit(1)
+        return
       }
     }
   }
@@ -256,16 +256,15 @@ export function applyPatchesForPackage({
         continue
       }
 
-      if (
-        applyPatch({
-          patchFilePath: join(appPath, patchDir, patchFilename) as string,
-          reverse,
-          patchDetails,
-          patchDir,
-          cwd: process.cwd(),
-          bestEffort,
-        })
-      ) {
+      const res = applyPatch({
+        patchFilePath: join(appPath, patchDir, patchFilename) as string,
+        reverse,
+        patchDetails,
+        patchDir,
+        cwd: process.cwd(),
+        bestEffort,
+      })
+      if (res.ok) {
         appliedPatches.push(patchDetails)
         // yay patch was applied successfully
         // print warning if version mismatch
@@ -281,15 +280,12 @@ export function applyPatchesForPackage({
           )
         }
         logPatchApplication(patchDetails)
-      } else if (patches.length > 1) {
-        logPatchSequenceError({ patchDetails })
-        // in case the package has multiple patches, we need to break out of this inner loop
-        // because we don't want to apply more patches on top of the broken state
-        failedPatch = patchDetails
-        break packageLoop
+        continue
+      }
+      failedPatch = patchDetails
+      if (patches.length > 1) {
+        errors.push(createPatchSequenceError({ patchDetails }))
       } else if (installedPackageVersion === version) {
-        // completely failed to apply patch
-        // TODO: propagate useful error messages from patch application
         errors.push(
           createBrokenPatchFileError({
             packageName: name,
@@ -298,7 +294,6 @@ export function applyPatchesForPackage({
             path,
           }),
         )
-        break packageLoop
       } else {
         errors.push(
           createPatchApplicationFailureError({
@@ -310,9 +305,9 @@ export function applyPatchesForPackage({
             pathSpecifier,
           }),
         )
-        // in case the package has multiple patches, we need to break out of this inner loop
-        // because we don't want to apply more patches on top of the broken state
-        break packageLoop
+      }
+      if (res.error.length) {
+        errors.push(`\n\n${res.error.map((e) => `  - ${e}`).join("\n")}`)
       }
     } catch (error) {
       if (error instanceof PatchApplicationError) {
@@ -325,10 +320,10 @@ export function applyPatchesForPackage({
           }),
         )
       }
-      // in case the package has multiple patches, we need to break out of this inner loop
-      // because we don't want to apply more patches on top of the broken state
-      break packageLoop
     }
+
+    // if we got to the end here it means we failed to apply the patch
+    break packageLoop
   }
 
   if (patches.length > 1) {
@@ -392,9 +387,6 @@ export function applyPatchesForPackage({
         isRebasing: !!failedPatch,
       })
     }
-    if (failedPatch) {
-      process.exit(1)
-    }
   }
 }
 
@@ -412,7 +404,7 @@ export function applyPatch({
   patchDir: string
   cwd: string
   bestEffort: boolean
-}): boolean {
+}): Result<true, string[]> {
   const patch = readPatch({
     patchFilePath,
     patchDetails,
@@ -427,12 +419,7 @@ export function applyPatch({
     const errors: string[] | undefined = bestEffort ? [] : undefined
     executeEffects(forward, { dryRun: false, cwd, bestEffort, errors })
     if (errors?.length) {
-      console.log(
-        "Saving errors to",
-        chalk.cyan.bold("./patch-package-errors.log"),
-      )
-      writeFileSync("patch-package-errors.log", errors.join("\n\n"))
-      process.exit(0)
+      return Result.err(errors)
     }
   } catch (e) {
     try {
@@ -443,11 +430,11 @@ export function applyPatch({
         bestEffort: false,
       })
     } catch (e) {
-      return false
+      return Result.err(["Failed to apply patch file: " + patchFilePath])
     }
   }
 
-  return true
+  return Result.ok(true)
 }
 
 function createVersionMismatchWarning({
diff --git a/src/makePatch.ts b/src/makePatch.ts
index 7e008eb3..9558e451 100644
--- a/src/makePatch.ts
+++ b/src/makePatch.ts
@@ -279,7 +279,7 @@ export function makePatch({
           reverse: false,
           cwd: tmpRepo.name,
           bestEffort: false,
-        })
+        }).ok
       ) {
         // TODO: add better error message once --rebase is implemented
         console.log(
@@ -500,10 +500,10 @@ export function makePatch({
               reverse: false,
               cwd: process.cwd(),
               bestEffort: false,
-            })
+            }).ok
           ) {
             didFailWhileFinishingRebase = true
-            logPatchSequenceError({ patchDetails: patch })
+            console.log(createPatchSequenceError({ patchDetails: patch }))
             nextState.push({
               patchFilename: patch.patchFilename,
               didApply: false,
@@ -577,12 +577,12 @@ function createPatchFileName({
   return `${nameAndVersion}${num}${name}.patch`
 }
 
-export function logPatchSequenceError({
+export function createPatchSequenceError({
   patchDetails,
 }: {
   patchDetails: PatchedPackageDetails
 }) {
-  console.log(`
+  return `
 ${chalk.red.bold("⛔ ERROR")}
 
 Failed to apply patch file ${chalk.bold(patchDetails.patchFilename)}.
@@ -602,5 +602,5 @@ After which you should make any required changes inside ${
   ${chalk.bold(`patch-package ${patchDetails.pathSpecifier}`)}
 
 to update the patch file.
-`)
+`
 }