0
# Error Handling
1
2
Comprehensive error handling system with task state management, error collection modes, rollback capabilities, and detailed error reporting for robust task execution.
3
4
## Capabilities
5
6
### ListrError Class
7
8
Main error class that wraps errors with context information and execution path details.
9
10
```typescript { .api }
11
/**
12
* Main error class for Listr task failures
13
* Provides context and path information for debugging
14
* @template Ctx - Context type
15
*/
16
class ListrError<Ctx> extends Error {
17
/** Execution path where error occurred */
18
public path: string[];
19
/** Task context when error occurred */
20
public ctx: Ctx;
21
/** Original error that was wrapped */
22
public error: Error;
23
/** Error type classification */
24
public type: ListrErrorTypes;
25
/** Task that generated the error */
26
public task: Task<Ctx, any, any>;
27
28
/**
29
* Create a new ListrError
30
* @param error - Original error that occurred
31
* @param type - Classification of the error type
32
* @param task - Task instance where error occurred
33
*/
34
constructor(error: Error, type: ListrErrorTypes, task: Task<Ctx, any, any>);
35
36
/**
37
* Get formatted error message with context
38
* @returns Formatted error string
39
*/
40
toString(): string;
41
42
/**
43
* Get error details as JSON
44
* @returns Serializable error information
45
*/
46
toJSON(): {
47
message: string;
48
path: string[];
49
type: ListrErrorTypes;
50
error: {
51
name: string;
52
message: string;
53
stack?: string;
54
};
55
};
56
}
57
```
58
59
### PromptError Class
60
61
Specialized error class for prompt-related failures and user interaction errors.
62
63
```typescript { .api }
64
/**
65
* Error class for prompt-related failures
66
* Used when user prompts fail or are cancelled
67
*/
68
class PromptError extends Error {
69
/** Original prompt error if available */
70
public originalError?: Error;
71
72
/**
73
* Create a new PromptError
74
* @param message - Error message
75
* @param originalError - Optional original error that caused prompt failure
76
*/
77
constructor(message: string, originalError?: Error);
78
}
79
```
80
81
### Error Types Classification
82
83
Enumeration of different error types for categorizing failures and determining handling strategies.
84
85
```typescript { .api }
86
/**
87
* Error type classifications for different failure scenarios
88
*/
89
enum ListrErrorTypes {
90
/** Task will be retried after this error */
91
WILL_RETRY = 'WILL_RETRY',
92
/** Task will be rolled back after this error */
93
WILL_ROLLBACK = 'WILL_ROLLBACK',
94
/** Task failed during rollback operation */
95
HAS_FAILED_TO_ROLLBACK = 'HAS_FAILED_TO_ROLLBACK',
96
/** Task has failed permanently */
97
HAS_FAILED = 'HAS_FAILED',
98
/** Task failed without throwing an error */
99
HAS_FAILED_WITHOUT_ERROR = 'HAS_FAILED_WITHOUT_ERROR'
100
}
101
```
102
103
### Task State Management
104
105
Task state enumeration covering all possible states including error and recovery states.
106
107
```typescript { .api }
108
/**
109
* Complete enumeration of task execution states
110
*/
111
enum ListrTaskState {
112
/** Task is waiting to be executed */
113
WAITING = 'WAITING',
114
/** Task has started execution */
115
STARTED = 'STARTED',
116
/** Task completed successfully */
117
COMPLETED = 'COMPLETED',
118
/** Task failed with an error */
119
FAILED = 'FAILED',
120
/** Task was skipped */
121
SKIPPED = 'SKIPPED',
122
/** Task is performing rollback operations */
123
ROLLING_BACK = 'ROLLING_BACK',
124
/** Task has been rolled back */
125
ROLLED_BACK = 'ROLLED_BACK',
126
/** Task is retrying after a failure */
127
RETRY = 'RETRY',
128
/** Task execution is paused */
129
PAUSED = 'PAUSED',
130
/** Task is waiting for user prompt input */
131
PROMPT = 'PROMPT',
132
/** Task prompt completed successfully */
133
PROMPT_COMPLETED = 'PROMPT_COMPLETED',
134
/** Task prompt failed */
135
PROMPT_FAILED = 'PROMPT_FAILED'
136
}
137
```
138
139
### Error Collection Options
140
141
Configuration options for how errors are collected and reported during task execution.
142
143
```typescript { .api }
144
/**
145
* Error collection configuration in Listr options
146
*/
147
interface ListrBaseClassOptions<Ctx, Renderer, FallbackRenderer> {
148
/**
149
* Error collection mode
150
* - false: Don't collect errors (exit immediately)
151
* - 'minimal': Collect basic error information
152
* - 'full': Collect detailed error information including stack traces
153
*/
154
collectErrors?: false | 'minimal' | 'full';
155
156
/** Exit immediately when any task fails */
157
exitOnError?: boolean;
158
159
/** Exit after rollback operations complete */
160
exitAfterRollback?: boolean;
161
}
162
```
163
164
**Usage Examples:**
165
166
### Basic Error Handling
167
168
```typescript
169
import { Listr, ListrError } from "listr2";
170
171
const tasks = new Listr([
172
{
173
title: "Task that might fail",
174
task: () => {
175
if (Math.random() < 0.5) {
176
throw new Error("Random failure occurred");
177
}
178
return "Success";
179
}
180
},
181
{
182
title: "Task that always runs",
183
task: () => {
184
console.log("This task always executes");
185
}
186
}
187
], {
188
exitOnError: false, // Continue execution even if tasks fail
189
collectErrors: 'full' // Collect detailed error information
190
});
191
192
try {
193
await tasks.run();
194
console.log("All tasks completed");
195
} catch (error) {
196
if (error instanceof ListrError) {
197
console.log("Listr execution failed:");
198
console.log("Path:", error.path);
199
console.log("Type:", error.type);
200
console.log("Original error:", error.error.message);
201
}
202
}
203
```
204
205
### Retry with Error Handling
206
207
```typescript
208
import { Listr, ListrErrorTypes } from "listr2";
209
210
const tasks = new Listr([
211
{
212
title: "Flaky network request",
213
retry: {
214
tries: 3,
215
delay: 1000
216
},
217
task: async (ctx, task) => {
218
const attempt = (ctx.attempt || 0) + 1;
219
ctx.attempt = attempt;
220
221
task.output = `Attempt ${attempt}`;
222
223
// Simulate network request that fails 70% of the time
224
if (Math.random() < 0.7) {
225
throw new Error(`Network request failed (attempt ${attempt})`);
226
}
227
228
task.output = `Success on attempt ${attempt}`;
229
return "Request completed";
230
}
231
}
232
]);
233
234
// Monitor retry attempts
235
tasks.events.on('TASK_ERROR', (event) => {
236
console.log(`Task failed: ${event.error.message}`);
237
if (event.willRetry) {
238
console.log(`Will retry (${event.retryCount + 1} attempts so far)`);
239
} else {
240
console.log("No more retries, task has failed permanently");
241
}
242
});
243
244
try {
245
await tasks.run();
246
} catch (error) {
247
console.log("Final error:", error.message);
248
}
249
```
250
251
### Rollback Functionality
252
253
```typescript
254
import { Listr } from "listr2";
255
256
interface DeployContext {
257
backupId?: string;
258
deploymentId?: string;
259
serverStarted?: boolean;
260
}
261
262
const deployTasks = new Listr<DeployContext>([
263
{
264
title: "Create backup",
265
task: (ctx) => {
266
ctx.backupId = `backup-${Date.now()}`;
267
console.log(`Created backup: ${ctx.backupId}`);
268
}
269
},
270
{
271
title: "Deploy application",
272
task: (ctx) => {
273
ctx.deploymentId = `deploy-${Date.now()}`;
274
275
// Simulate deployment failure
276
if (Math.random() < 0.4) {
277
throw new Error("Deployment failed - server error");
278
}
279
280
console.log(`Deployed: ${ctx.deploymentId}`);
281
},
282
rollback: (ctx, task) => {
283
if (ctx.backupId && ctx.deploymentId) {
284
task.output = `Rolling back deployment ${ctx.deploymentId}...`;
285
console.log(`Restoring from backup: ${ctx.backupId}`);
286
// Simulate rollback operations
287
return new Promise(resolve => setTimeout(resolve, 1000));
288
}
289
}
290
},
291
{
292
title: "Start services",
293
task: (ctx) => {
294
ctx.serverStarted = true;
295
console.log("Services started");
296
},
297
rollback: (ctx, task) => {
298
if (ctx.serverStarted) {
299
task.output = "Stopping services...";
300
console.log("Services stopped");
301
ctx.serverStarted = false;
302
}
303
}
304
}
305
], {
306
exitAfterRollback: true // Exit after rollback operations complete
307
});
308
309
try {
310
await deployTasks.run();
311
console.log("Deployment successful");
312
} catch (error) {
313
console.log("Deployment failed and rolled back:", error.message);
314
}
315
```
316
317
### Error Collection and Reporting
318
319
```typescript
320
import { Listr, ListrError } from "listr2";
321
322
const tasks = new Listr([
323
{
324
title: "Task 1",
325
task: () => {
326
throw new Error("First task error");
327
}
328
},
329
{
330
title: "Task 2",
331
task: () => {
332
throw new Error("Second task error");
333
}
334
},
335
{
336
title: "Task 3",
337
task: () => {
338
console.log("Task 3 executes successfully");
339
}
340
}
341
], {
342
exitOnError: false,
343
collectErrors: 'full'
344
});
345
346
try {
347
await tasks.run();
348
} catch (error) {
349
if (error instanceof ListrError) {
350
console.log("\n=== Error Summary ===");
351
console.log(`Main error: ${error.message}`);
352
console.log(`Error type: ${error.type}`);
353
console.log(`Execution path: ${error.path.join(' → ')}`);
354
355
// Access all collected errors
356
if (tasks.errors.length > 0) {
357
console.log("\n=== All Errors ===");
358
tasks.errors.forEach((err, index) => {
359
console.log(`${index + 1}. ${err.message}`);
360
console.log(` Path: ${err.path.join(' → ')}`);
361
console.log(` Type: ${err.type}`);
362
console.log(` Original: ${err.error.message}`);
363
});
364
}
365
}
366
}
367
```
368
369
### Custom Error Handling per Task
370
371
```typescript
372
import { Listr } from "listr2";
373
374
interface ProcessingContext {
375
strictMode: boolean;
376
errors: string[];
377
}
378
379
const tasks = new Listr<ProcessingContext>([
380
{
381
title: "Critical system task",
382
exitOnError: true, // Always exit if this task fails
383
task: () => {
384
if (Math.random() < 0.2) {
385
throw new Error("Critical system failure");
386
}
387
}
388
},
389
{
390
title: "Optional enhancement",
391
exitOnError: false, // Never exit for this task
392
task: () => {
393
throw new Error("Enhancement failed, but continuing");
394
}
395
},
396
{
397
title: "Context-dependent task",
398
exitOnError: (ctx) => ctx.strictMode, // Exit based on context
399
task: (ctx) => {
400
if (Math.random() < 0.3) {
401
const error = "Context-dependent failure";
402
ctx.errors.push(error);
403
throw new Error(error);
404
}
405
}
406
}
407
]);
408
409
await tasks.run({
410
strictMode: false,
411
errors: []
412
});
413
```
414
415
### Prompt Error Handling
416
417
```typescript
418
import { Listr, PromptError } from "listr2";
419
420
const tasks = new Listr([
421
{
422
title: "Interactive configuration",
423
task: async (ctx, task) => {
424
try {
425
// Simulate prompt interaction
426
const userInput = await new Promise((resolve, reject) => {
427
setTimeout(() => {
428
// Simulate user cancellation or timeout
429
if (Math.random() < 0.3) {
430
reject(new PromptError("User cancelled the prompt"));
431
} else {
432
resolve("user-input-value");
433
}
434
}, 1000);
435
});
436
437
ctx.userConfig = userInput;
438
task.output = "Configuration completed";
439
} catch (error) {
440
if (error instanceof PromptError) {
441
task.output = "Prompt was cancelled, using defaults";
442
ctx.userConfig = "default-value";
443
} else {
444
throw error; // Re-throw non-prompt errors
445
}
446
}
447
}
448
}
449
]);
450
```
451
452
### Advanced Error Analysis
453
454
```typescript
455
import { Listr, ListrError, ListrErrorTypes, ListrTaskState } from "listr2";
456
457
class ErrorAnalyzer {
458
private errors: ListrError<any>[] = [];
459
private retryCount = 0;
460
private rollbackCount = 0;
461
462
analyze(tasks: Listr<any>) {
463
// Listen to various error events
464
tasks.events.on('TASK_ERROR', (event) => {
465
this.retryCount++;
466
console.log(`Retry ${this.retryCount}: ${event.error.message}`);
467
});
468
469
tasks.events.on('TASK_STATE_CHANGED', (event) => {
470
if (event.newState === ListrTaskState.ROLLING_BACK) {
471
this.rollbackCount++;
472
console.log(`Rollback ${this.rollbackCount} initiated for task ${event.taskId}`);
473
}
474
});
475
}
476
477
generateReport(tasksErrors: ListrError<any>[]) {
478
const report = {
479
totalErrors: tasksErrors.length,
480
retryAttempts: this.retryCount,
481
rollbackOperations: this.rollbackCount,
482
errorsByType: {} as Record<ListrErrorTypes, number>,
483
errorsByPath: {} as Record<string, number>
484
};
485
486
tasksErrors.forEach(error => {
487
// Count by error type
488
report.errorsByType[error.type] = (report.errorsByType[error.type] || 0) + 1;
489
490
// Count by execution path
491
const pathKey = error.path.join(' → ');
492
report.errorsByPath[pathKey] = (report.errorsByPath[pathKey] || 0) + 1;
493
});
494
495
return report;
496
}
497
}
498
499
// Usage
500
const analyzer = new ErrorAnalyzer();
501
const tasks = new Listr([
502
// ... tasks with potential failures
503
], {
504
collectErrors: 'full',
505
exitOnError: false
506
});
507
508
analyzer.analyze(tasks);
509
510
try {
511
await tasks.run();
512
} catch (error) {
513
const report = analyzer.generateReport(tasks.errors);
514
console.log("Error Analysis Report:", JSON.stringify(report, null, 2));
515
}
516
```
517
518
## Types
519
520
```typescript { .api }
521
/**
522
* Error context information
523
*/
524
interface ListrErrorContext {
525
/** Task execution path */
526
path: string[];
527
/** Task context at time of error */
528
context: any;
529
/** Timestamp when error occurred */
530
timestamp: Date;
531
/** Whether task will be retried */
532
willRetry: boolean;
533
/** Current retry attempt number */
534
retryAttempt: number;
535
/** Maximum retry attempts allowed */
536
maxRetries: number;
537
}
538
539
/**
540
* Rollback operation result
541
*/
542
interface RollbackResult {
543
/** Whether rollback was successful */
544
success: boolean;
545
/** Error that occurred during rollback, if any */
546
error?: Error;
547
/** Duration of rollback operation */
548
duration: number;
549
/** Additional rollback metadata */
550
metadata?: Record<string, any>;
551
}
552
553
/**
554
* Error collection summary
555
*/
556
interface ErrorSummary {
557
/** Total number of errors */
558
totalErrors: number;
559
/** Errors by type */
560
byType: Record<ListrErrorTypes, ListrError<any>[]>;
561
/** Errors by execution path */
562
byPath: Record<string, ListrError<any>[]>;
563
/** Tasks that were retried */
564
retriedTasks: string[];
565
/** Tasks that were rolled back */
566
rolledBackTasks: string[];
567
}
568
```