Skip to content

Commit 8ebd9cc

Browse files
committed
refactor(error display): apply cause-chain walk to typed-error branches
Extract the .cause walk into `appendCauseChain` and use it from every Error-instanceof branch, not just the generic fallback. AuthError / NetworkError / FileSystemError / ConfigError / InputError / RateLimitError constructors don't currently forward `{ cause }`, so the helper is a no-op for them today — but it future-proofs the display layer so the day a typed error does carry a cause, it surfaces without another edit.
1 parent 76b2f0d commit 8ebd9cc

File tree

1 file changed

+26
-19
lines changed

1 file changed

+26
-19
lines changed

packages/cli/src/utils/error/display.mts

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,25 @@ export type ErrorDisplayOptions = {
2525
verbose?: boolean | undefined
2626
}
2727

28+
/**
29+
* Append the `.cause` chain (up to 5 levels) to a base message so
30+
* non-debug users see the diagnostic context from wrapped errors.
31+
* Typed errors (AuthError, NetworkError, …) can also carry causes.
32+
*/
33+
function appendCauseChain(baseMessage: string, cause: unknown): string {
34+
const plainCauses: string[] = []
35+
let walk: unknown = cause
36+
let walkDepth = 1
37+
while (walk && walkDepth <= 5) {
38+
plainCauses.push(walk instanceof Error ? walk.message : String(walk))
39+
walk = walk instanceof Error ? walk.cause : undefined
40+
walkDepth++
41+
}
42+
return plainCauses.length
43+
? `${baseMessage}: ${plainCauses.join(': ')}`
44+
: baseMessage
45+
}
46+
2847
/**
2948
* Format an error for display with polish and clarity.
3049
* Uses LOG_SYMBOLS and colors for visual hierarchy.
@@ -47,50 +66,38 @@ export function formatErrorForDisplay(
4766
if (error.retryAfter) {
4867
message += ` (retry after ${error.retryAfter}s)`
4968
}
69+
message = appendCauseChain(message, error.cause)
5070
} else if (error instanceof AuthError) {
5171
title = 'Authentication error'
52-
message = error.message
72+
message = appendCauseChain(error.message, error.cause)
5373
} else if (error instanceof NetworkError) {
5474
title = 'Network error'
5575
message = error.message
5676
if (error.statusCode) {
5777
message += ` (HTTP ${error.statusCode})`
5878
}
79+
message = appendCauseChain(message, error.cause)
5980
} else if (error instanceof FileSystemError) {
6081
title = 'File system error'
6182
message = error.message
6283
if (error.path) {
6384
message += ` (${error.path})`
6485
}
86+
message = appendCauseChain(message, error.cause)
6587
} else if (error instanceof ConfigError) {
6688
title = 'Configuration error'
6789
message = error.message
6890
if (error.configKey) {
6991
message += ` (key: ${error.configKey})`
7092
}
93+
message = appendCauseChain(message, error.cause)
7194
} else if (error instanceof InputError) {
7295
title = 'Invalid input'
73-
message = error.message
96+
message = appendCauseChain(error.message, error.cause)
7497
body = error.body
7598
} else if (error instanceof Error) {
7699
title = opts.title || 'Unexpected error'
77-
// Concatenate the cause chain into `message` (what non-debug users see)
78-
// so diagnostic context from wrapped errors isn't silently dropped.
79-
// `showStack` adds a richer formatted body with stack traces below.
80-
message = error.message
81-
const plainCauses: string[] = []
82-
let walk: unknown = error.cause
83-
let walkDepth = 1
84-
while (walk && walkDepth <= 5) {
85-
plainCauses.push(
86-
walk instanceof Error ? walk.message : String(walk),
87-
)
88-
walk = walk instanceof Error ? walk.cause : undefined
89-
walkDepth++
90-
}
91-
if (plainCauses.length) {
92-
message = `${message}: ${plainCauses.join(': ')}`
93-
}
100+
message = appendCauseChain(error.message, error.cause)
94101

95102
if (showStack && error.stack) {
96103
// Format stack trace with proper indentation.

0 commit comments

Comments
 (0)