Skip to content

Error Handling

Exceptions that occur while running the Task will be handled internally through Listr. You can throw errors out of the tasks to show they are unsuccessful or stop execution. This can further be customized at Listr, Subtask or Task level.

Errors will yield a visual output on the terminal depending on the current renderer, and will also handle the Task that has failed depending on the configuration. If an application needs to quit prematurely and fail a specific task just throw out an instance of Error.

The default behavior is if any of the tasks have failed, it will deem itself as unsuccessful and exit. This behavior can be changed with the exitOnError option. If the exitOnError is true, the first error encountered will be thrown out, and it will propagate outwards starting from the Task.

WARNING

An Error should be always a real Error type extended from the JavaScript/Typescript Error class.

Example

You can find the related examples here.

Throwing a Error

Throwing an error will stop any further action from the current Task and will propagate outwards of the Task to Listr and depending on the exitOnError configuration, execution will be slowly halted for upcoming or concurrent tasks.

INFO

You don't have to catch and collect errors explicitly since they will always be collected by Listr.

WARNING

Be aware that the execution will only stop after the error is thrown out. This can kill any asynchronous action prematurely.

ts
import { delay, Listr } from 'listr2'

const tasks = new Listr(
  [
    {
      title: 'This task will fail.',
      task: async (): Promise<void> => {
        await delay(2000)
        throw new Error('This task failed after 2 seconds.')
      }
    },
    {
      title: 'This task will never execute.',
      task: (ctx, task): void => {
        task.title = 'I will change my title if this executes.'
      }
    }
  ],
  { concurrent: false }
)

await tasks.run()

Changing the Behavior

Per Listr

ts
import { delay, Listr } from 'listr2'

const tasks = new Listr(
  [
    {
      title: 'This task will fail.',
      task: async (): Promise<void> => {
        await delay(2000)
        throw new Error('This task failed after 2 seconds.')
      }
    },
    {
      title: 'This task will execute.',
      task: (ctx, task): void => {
        task.title = 'I will change my title if this executes.'
      }
    }
  ],
  { concurrent: false, exitOnError: false }
)

await tasks.run()

Per Subtask

ts
import { delay, Listr } from 'listr2'

const tasks = new Listr(
  [
    {
      title: 'This task will execute and not quit on errors.',
      task: (ctx, task): Listr =>
        task.newListr(
          [
            {
              title: 'This is a subtask.',
              task: async (): Promise<void> => {
                throw new Error('I have failed [0]')
              }
            },
            {
              title: 'This is yet an another subtask and it will run.',
              task: async (ctx, task): Promise<void> => {
                task.title = 'I have succeeded.'
              }
            }
          ],
          { exitOnError: false }
        )
    },
    {
      title: 'This task will execute.',
      task: (): void => {
        throw new Error('I will exit on error since I am a direct child of parent task.')
      }
    }
  ],
  { concurrent: false, exitOnError: true }
)

await tasks.run()

Per Task

ts
import { delay, Listr } from 'listr2'

const tasks = new Listr(
  [
    {
      title: 'This task will fail.',
      task: async (): Promise<void> => {
        await delay(2000)
        throw new Error('This task failed after 2 seconds.')
      },
      exitOnError: false
    },
    {
      title: 'This task will execute.',
      task: (_, task): void => {
        task.title = 'I will change my title if this executes.'
      }
    }
  ],
  {
    concurrent: false,
    exitOnError: true
  }
)

await tasks.run()

Renderer

DefaultRenderer

Default renderer has options where you can change how the errors are displayed.

Interface

showErrorMessage?

optional showErrorMessage: boolean

Show the error message or show the original title of the task.

  • true will output the current error encountered with the task if there is any.
  • false will keep the current task title intact. This will also disable collapseErrors.

Default Value

true

Source

packages/listr2/src/renderer/default/renderer.interface.ts:118


collapseErrors?

optional collapseErrors: boolean

Collapse error messages into a single message and overwrite the task title.

  • true will collapse the error message.
  • false will show the error message as a data output under the current task title.

Default Value

true

Source

packages/listr2/src/renderer/default/renderer.interface.ts:127

Collected Errors

Errors from the Task are collected inside an array in the main Listr task list as tasks.error where tasks is the Listr class. This option is opt-in since v6.0.0.

Since there are options to ignore some errors on cases like exitOnError, or the ability to retry the given task through task.retry, encountered errors can be swallowed while the execution. To deal with those swallowed errors, all the errors that are encountered even though it does not stops the execution gets collected through this property.

Modes

#615

Error collection has three modes to choose from which are, false, minimal and full. This can be set through per Task in the Listr options with the key collectErrors. The default mode is false since I decided that this is the most-underused functionality, and it should be at least opt-in for saving some memory.

Due to potential memory leaks from cloning the context and task to the ListrError, advised mode is minimal, which will only collect where the error has occurred, when it has been encountered and what the error.message is.

If you want to fetch the full information for debugging you can set the mode to full. This will also clone the current context and task to the ListrError.

You can disable the error collection completely by setting it to false.

ListrError

ListrError class extends the default Error and has some additional information like the cause of the error and where it is coming from, and the frozen context at the given time to further debug the issue while execution.

ListrErrorTypes

A listr error can be caused by multiple reasons, for a better explanation of why that particular error occurred, a type property on the ListrError exists in the form of enum ListrErrorTypes.

Methodology

The order of the array tasks.error where tasks is the Listr class, represents the order of errors that are encountered.

To keep the error collection mechanism simple and predictable, it might also process the errors coming from the subtasks as well.

For example, the following example will clear some things up about the given mindset.

Code
ts
import { Listr } from 'listr2'

const tasks = new Listr(
  [
    {
      task: (): void => {
        throw new Error('1')
      }
    },
    {
      task: (_, task): Listr => {
        return task.newListr(
          [
            {
              task: (): void => {
                throw new Error('3')
              }
            },
            {
              task: (): void => {
                throw new Error('4')
              }
            }
          ],
          { exitOnError: true }
        )
      }
    },
    {
      task: (): void => {
        throw new Error('2')
      }
    }
  ],
  { exitOnError: false, collectErrors: 'minimal' }
)

await tasks.run()

console.log(tasks.errors)
Output
bash
[
  Error: 1
      at Task.taskFn (file:///home/cenk/development/listr2/examples/docs/task/error-handling/collection.ts:7:15)
      at Task.run (file:///home/cenk/development/listr2/src/lib/task.ts:298:35) {
    error: Error: 1
        at Task.taskFn (file:///home/cenk/development/listr2/examples/docs/task/error-handling/collection.ts:7:15)
        at Task.run (file:///home/cenk/development/listr2/src/lib/task.ts:298:35),
    type: 'HAS_FAILED_WITHOUT_ERROR',
    task: Task {
      emitter: [EventEmitter],
      listr: [Listr],
      task: [Object],
      options: [Object],
      rendererOptions: {},
      id: '5.1ffae60d8631f.efd766868b915.866e1eec8dcb1.6c49d60beb37d.a3a892931a5ef.e78a8650db4c1.74a7a66109265.bcc1d65fd77f-9.16ae5bbd949de.84a3445d953d9.b4ff3c4de3d24.6847e6b03009-48.845a4ab1c8fec.0406e1d03a734.306961606f12-3f.d63122b38bc71.2c6be88b853d.aa5a66a00
fa3-2.67a25dc32b275.05069591f2917.99ad61f61433b.62d2b3fcf014.6c3ab12e8638b.a012e95dfadb6.5ac0b32dde1f5.3783719aafae9.f7cf95351bc3.b468fee844f0.c9627515807f4.3c546e82beb7',
      state: 'FAILED',
      subtasks: undefined,
      title: undefined,
      initialTitle: undefined,
      output: undefined,
      retry: undefined,
      message: [Object],
      rendererTaskOptions: undefined,
      prompt: undefined,
      parent: undefined,
      enabled: true,
      taskFn: [Function: task]
    },
    path: [ undefined ],
    ctx: undefined,
    name: 'ListrError'
  },
  Error: 3
      at Task.taskFn (file:///home/cenk/development/listr2/examples/docs/task/error-handling/collection.ts:16:23)
      at Task.run (file:///home/cenk/development/listr2/src/lib/task.ts:298:35) {
    error: Error: 3
        at Task.taskFn (file:///home/cenk/development/listr2/examples/docs/task/error-handling/collection.ts:16:23)
        at Task.run (file:///home/cenk/development/listr2/src/lib/task.ts:298:35),
    type: 'HAS_FAILED',
    task: Task {
      emitter: [EventEmitter],
      listr: [Listr],
      task: [Object],
      options: [Object],
      rendererOptions: {},
      id: 'e.b91e31c7a8633.a5c0d756a30ed.8653c1351752e.73b560a042c80.64e107220cbf3.fc2b7bd6f02f5.c9688ca346799.cb51fffc2226-a.6e976cd950398.b054762b266ad.464a3f3110918.e466da24f4d6-45.96ed406986ecb.11850cc6105df.560383a2d91c-36.0c1b392d14abe.9f89babe09822.45b315ac
4bb8-1.ee031a687fb11.86d5245ac9fc3.55e9067a7e183.53d94301c813.9591c72743037.4922af1ed491f.2bdb77eed4ee.ec002d1dfb8e1.7882a17eb2377.049051202a2e0.0c942ce10bc6b.0d33f80a6803',
      state: 'FAILED',
      subtasks: undefined,
      title: undefined,
      initialTitle: undefined,
      output: undefined,
      retry: undefined,
      message: [Object],
      rendererTaskOptions: undefined,
      prompt: undefined,
      parent: [Task],
      enabled: true,
      taskFn: [Function: task]
    },
    path: [ undefined, undefined ],
    ctx: undefined,
    name: 'ListrError'
  },
  Error: 2
      at Task.taskFn (file:///home/cenk/development/listr2/examples/docs/task/error-handling/collection.ts:31:15)
      at Task.run (file:///home/cenk/development/listr2/src/lib/task.ts:298:35) {
    error: Error: 2
        at Task.taskFn (file:///home/cenk/development/listr2/examples/docs/task/error-handling/collection.ts:31:15)
        at Task.run (file:///home/cenk/development/listr2/src/lib/task.ts:298:35),
    type: 'HAS_FAILED_WITHOUT_ERROR',
    task: Task {
      emitter: [EventEmitter],
      listr: [Listr],
      task: [Object],
      options: [Object],
      rendererOptions: {},
      id: '8.255d2f2e3fa9c.8870108a62aee.14a04d8597aa4.da8da95f37b79.575d8732ea0b5.cb0d308db7b40.f19fb8db928d.5ed830c8904b-e.b944062c0ab5f.d339986948bdf.5ad93c6148292.01e745100f4c-47.fe4e954aec826.032487f91f53b.df78f4442433-31.591906fa76151.dbe812f35d5a8.17d8b12a5
3c8-d.6919e94a5b5d7.e0f5853c6cc2e.c1020891d643a.1235a6678d71d.f39b92415cafb.0c07b9db54fc0.02522dfb9af63.c2fd91995b050.ef96265e70f54.d2ad4fff8edd0.ed4249a0f67f6.b78b6510ccf9',
      state: 'FAILED',
      subtasks: undefined,
      title: undefined,
      initialTitle: undefined,
      output: undefined,
      retry: undefined,
      message: [Object],
      rendererTaskOptions: undefined,
      prompt: undefined,
      parent: undefined,
      enabled: true,
      taskFn: [Function: task]
    },
    path: [ undefined ],
    ctx: undefined,
    name: 'ListrError'
  }
]
Flow
  • First error will be thrown from the first task. Since exitOnError is false on that context, ListrError will get collected by tasks.errors], and the value will be { message: '1', type: ListrErrorTypes.HAS_FAILED_WITHOUT_ERROR }.
  • Then it will recurse into the second task, which has two subtasks.
  • The first task from the subtasks will fail and since the exitOnError is set to true in that context, that subtasks will fail and throw. The ListrError appended to the tasks.errors will be { message: '3', type: ListrErrorTypes.HAS_FAILED }
  • Since the subtask has crashed, it will not execute the upcoming tasks in the subtasks.
  • It will return to the main task list and execute the 3rd task from that list. It will again show the same behavior with the first task, and the ListrError will be { message: '2', type: ListrErrorTypes.HAS_FAILED_WITHOUT_ERROR }.