---
url: https://listr2.kilic.dev/migration/v11.md
---

## Breaking Changes

These require action to migrate. Each entry shows what changed and how to update your code.

### `collectErrors` is now a boolean

The *Listr* option `collectErrors` no longer accepts `'minimal'` or `'full'` — it is a `boolean`. The `'full'` mode, which deep-cloned the context into every collected error, has been removed.

```typescript
// before
new Listr(tasks, { collectErrors: 'minimal' })

// after — 'minimal' becomes true; 'full' has no replacement
new Listr(tasks, { collectErrors: true })
```

### `Listr.errors` is `null` when collection is disabled

With the default `collectErrors: false`, `Listr.errors` is now `null` instead of an empty array. An empty array now unambiguously means "collection is enabled and nothing failed". Because the collection is shared from the root of the run, `collectErrors` is effectively root-scoped: enabling it only on a subtask while the root leaves it disabled no longer collects those errors.

```typescript
// before — always an array
if (listr.errors.length) {
  /* ... */
}

// after — null unless collectErrors is enabled
if (listr.errors?.length) {
  /* ... */
}
```

### `ListrError.ctx` has been removed

`ListrError` no longer clones the context into the error instance. Only the `error`, its `type`, and the `path` of the failed task are collected. Read the live `task` reference if you need more.

```typescript
// before
const context = listr.errors[0].ctx

// after — ctx is gone; the failed task is still available
const task = listr.errors[0].task
```

### `cloneObject` export and `rfdc` dependency removed

Both existed only for the removed `'full'` error collection mode. If you imported `cloneObject` from `listr2`, use `structuredClone` (or your own clone) instead.

### `eventemitter3` dropped for Node's built-in `EventEmitter`

The internal event handling now uses Node.js's built-in `EventEmitter` from `node:events`. The public API is unchanged; only act if you relied on `listr2` pulling in `eventemitter3` transitively — install it directly in that case.

### `Ctrl+C` now rolls back before exiting

Interrupting a run with `Ctrl+C` (`SIGINT`) now runs the `rollback` of any in-flight task that defines one and defers the `exit(127)` until every in-flight rollback has settled, instead of marking the pending tasks as failed and exiting synchronously. If you relied on the previous synchronous fail-and-exit, be aware that rollbacks now run first.

### New `ListrTaskState.CANCELLED` state

Tasks stopped by an interruption that do not roll back — and any tasks that had not started — are now marked `CANCELLED` (a `⊘` icon in the built-in renderers) instead of `FAILED`. A `task.isCancelled()` helper is available.

If you maintain a **custom renderer** that switches on the task state, handle the new state:

```typescript
switch (task.state) {
  // ...your existing cases

  case ListrTaskState.CANCELLED:
    // render the cancelled task
    break
}
```

If you assert against the **test renderer** in your own test suite, the serialized task now carries an `isCancelled` field and interrupted tasks report the `CANCELLED` state — regenerate your snapshots (for example `jest -u`).

## Features

* Expose the run's `AbortSignal` on the task wrapper as `task.signal`, so task functions can cooperatively cancel their own asynchronous work like `fetch`, child processes or timers when the run is interrupted.&#x20;
* Add `listr.cancel()` and `task.cancel()` to interrupt a run programmatically without an operating-system signal, rolling back or cancelling the in-flight tasks the same way `Ctrl+C` does.&#x20;
* Reset a task's streamed output by setting `task.output = null`, which clears what the task already streamed to the output bar and bottom bar in the default renderer.&#x20;
* Upgrade `log-update` to `v8` and `cli-truncate` to `v6`, enabling partial/differential rendering in the default renderer — the task list is redrawn with smaller, targeted terminal writes wrapped in synchronized-output markers (`ESC[?2026h`/`ESC[?2026l`) on supporting terminals, reducing flicker. The visible output is unchanged.
* Document routing all renderer output to `stderr` to keep `stdout` clean and pipeable. See [Process Output](../renderer/process-output.md).&#x20;

## Bug Fixes

* Fix a memory leak in the default renderer where a finalized task had its output buffer re-created and its listeners re-attached on every render, which exhausted the heap when another task streamed to `task.output` at a high rate over a long run.&#x20;
* Preserve OSC-8 hyperlinks, bells and other formatting in the task output, only stripping the cursor and erase codes that would disrupt the rendering.&#x20;
* Derive the default renderer's wrap width from the configured output stream instead of `process.stdout`, so routing the renderer to `stderr` wraps to the correct width.&#x20;
* `task.output = null` no longer renders the literal string `"null"`.&#x20;

## Node.js Support

The test and build matrix now covers Node.js 22, 24 and 26. The minimum supported version is unchanged at `>=22.13.0`.

::: warning

The optional `enquirer` peer dependency is effectively unmaintained, and a `readline` change in Node.js 24+ (`ERR_USE_AFTER_CLOSE`) breaks its teardown in some non-interactive scenarios. Adding Node.js 26 to the test matrix surfaced this. The `enquirer` prompt adapter is expected to keep working on Node.js 22 and 24, but **is no longer guaranteed on Node.js 26 and above** — prefer the [`inquirer`](../task/prompts.md#inquirer) adapter on newer Node.js versions.

:::

## Documentation

The documentation site now publishes an LLM-friendly form of the docs — `llms.txt`, `llms-full.txt`, and per-page Markdown — so AI assistants and agents can consume it directly. See [Agents](../repository/llms.md).
