Repository: vercel/next.js
File: packages/next/src/server/patch-error-inspect.ts (TypeScript)

Code before:
  errorConstructor: ErrorConstructor
): void {
  const inspectSymbol = Symbol.for('nodejs.util.inspect.custom')

  errorConstructor.prepareStackTrace = prepareUnsourcemappedStackTrace

  // @ts-expect-error -- TODO upstream types
  errorConstructor.prototype[inspectSymbol] = function (
    depth: number,
    inspectOptions: util.InspectOptions,
    inspect: typeof util.inspect
  ): string {
    // avoid false-positive dynamic i/o warnings e.g. due to usage of `Math.random` in `source-map`.
    return workUnitAsyncStorage.exit(() => {
      const newError = sourceMapError(this, inspectOptions)

      const originalCustomInspect = (newError as any)[inspectSymbol]
      // Prevent infinite recursion.
      // { customInspect: false } would result in `error.cause` not using our inspect.
      Object.defineProperty(newError, inspectSymbol, {
        value: undefined,
        enumerable: false,
        writable: true,
      })
      try {
        return inspect(newError, {
          ...inspectOptions,
          depth,
        })
      } finally {
        ;(newError as any)[inspectSymbol] = originalCustomInspect
      }
    })
  }
}

export function patchErrorInspectEdgeLite(
  errorConstructor: ErrorConstructor
): void {
  const inspectSymbol = Symbol.for('edge-runtime.inspect.custom')

  errorConstructor.prepareStackTrace = prepareUnsourcemappedStackTrace

  // @ts-expect-error -- TODO upstream types
  errorConstructor.prototype[inspectSymbol] = function ({
    format,
  }: {
    format: (...args: unknown[]) => string
  }): string {
    // avoid false-positive dynamic i/o warnings e.g. due to usage of `Math.random` in `source-map`.
    return workUnitAsyncStorage.exit(() => {

Review comment (bug):
This is another bug affecting both `AggregateError.errors` as well as `Error.cause`. `util.inspect` has a `depth` property which [defaults](https://nodejs.org/api/util.html#utilinspectobject-showhidden-depth-colors) to `2`, but it was being unnecessarily transformed here.

## `Error.cause`

```ts
const depth4 = new Error('Depth 4 error')
const depth3 = new Error('Depth 3 error', { cause: depth4 })
const depth2 = new Error('Depth 2 error', { cause: depth3 })
const depth1 = new Error('Depth 1 error', { cause: depth2 })
const depth0 = new Error('Depth 0 error', { cause: depth1 })
const formatted = util.inspect(depth0)
console.log(formatted);
```

Should print:

```
Error: Depth 0 error
    at Object.<anonymous> (packages/next/src/server/patch-error-inspect.test.ts:101:32)
    at new Promise (<anonymous>) {
  [cause]: Error: Depth 1 error
      at Object.<anonymous> (packages/next/src/server/patch-error-inspect.test.ts:98:32)
      at new Promise (<anonymous>) {
    [cause]: Error: Depth 2 error
        at Object.<anonymous> (packages/next/src/server/patch-error-inspect.test.ts:95:32)
        at new Promise (<anonymous>) {
      [cause]: [Error]
    }
  }
}
```

Note the truncation to `[cause]: [Error]` after depth 2. Instead, it would not respect the `depth: 2` argument for nested `cause` errors:

```
Error: Depth 0 error
    at Object.<anonymous> (packages/next/src/server/patch-error-inspect.test.ts:101:32)
    at new Promise (<anonymous>) {
  [cause]: Error: Depth 1 error
      at Object.<anonymous> (packages/next/src/server/patch-error-inspect.test.ts:98:32)
      at new Promise (<anonymous>) {
    [cause]: Error: Depth 2 error
        at Object.<anonymous> (packages/next/src/server/patch-error-inspect.test.ts:95:32)
        at new Promise (<anonymous>) {
      [cause]: Error: Depth 3 error
          at Object.<anonymous> (packages/next/src/server/patch-error-inspect.test.ts:92:32)
          at new Promise (<anonymous>) {
        [cause]: Error: Depth 4 error
            at Object.<anonymous> (packages/next/src/server/patch-error-inspect.test.ts:91:32)
            at new Promise (<anonymous>)
      }
    }
  }
}
```

## `AggregateError.errors`

```ts
const error1 = new Error('Error 1')
const error2 = new TypeError('Error 2')
const rootError = new Error('Root error')
const aggregateError = new AggregateError(
  [error1, error2],
  'Multiple errors:',
  { cause: rootError }
)
const formatted = util.inspect(aggregateError)
console.log(formatted)
```

Should print:

```
AggregateError: Multiple errors:
    at Object.<anonymous> (packages/next/src/server/patch-error-inspect.test.ts:121:40)
    at new Promise (<anonymous>) {
    [cause]: Error: Root error
        at Object.<anonymous> (packages/next/src/server/patch-error-inspect.test.ts:120:35)
        at new Promise (<anonymous>),
    [errors]: [
    Error: Error 1
        at Object.<anonymous> (packages/next/src/server/patch-error-inspect.test.ts:118:32)
        at new Promise (<anonymous>),
    TypeError: Error 2
        at Object.<anonymous> (packages/next/src/server/patch-error-inspect.test.ts:119:32)
        at new Promise (<anonymous>)
    ]
}
```

But in actuality, it would immediately truncate:

```
AggregateError: Multiple errors:
    at Object.<anonymous> (packages/next/src/server/patch-error-inspect.test.ts:121:40)
    at new Promise (<anonymous>) {
  [cause]: Error: Root error
      at Object.<anonymous> (packages/next/src/server/patch-error-inspect.test.ts:120:35)
      at new Promise (<anonymous>),
  [errors]: [Array]
}
```

This fix resolves both cases to match the expected behaviors.

Code after:
  const inspectSymbol = Symbol.for('nodejs.util.inspect.custom')

  errorConstructor.prepareStackTrace = prepareUnsourcemappedStackTrace

  // @ts-expect-error -- TODO upstream types
  errorConstructor.prototype[inspectSymbol] = function (
    depth: number,
    inspectOptions: util.InspectOptions,
    inspect: typeof util.inspect
  ): string {
    // avoid false-positive dynamic i/o warnings e.g. due to usage of `Math.random` in `source-map`.
    return workUnitAsyncStorage.exit(() => {
      const newError = sourceMapError(this, inspectOptions)

      const originalCustomInspect = (newError as any)[inspectSymbol]
      // Prevent infinite recursion.
      // { customInspect: false } would result in `error.cause` not using our inspect.
      Object.defineProperty(newError, inspectSymbol, {
        value: undefined,
        enumerable: false,
        writable: true,
      })
      try {
        return inspect(newError, {
          ...inspectOptions,
          depth,
        })
      } finally {
        ;(newError as any)[inspectSymbol] = originalCustomInspect
      }
    })
  }
}

export function patchErrorInspectEdgeLite(
  errorConstructor: ErrorConstructor
): void {
  const inspectSymbol = Symbol.for('edge-runtime.inspect.custom')

  errorConstructor.prepareStackTrace = prepareUnsourcemappedStackTrace

  // @ts-expect-error -- TODO upstream types
  errorConstructor.prototype[inspectSymbol] = function ({
    format,
  }: {
    format: (...args: unknown[]) => string
  }): string {
    // avoid false-positive dynamic i/o warnings e.g. due to usage of `Math.random` in `source-map`.
    return workUnitAsyncStorage.exit(() => {
      const newError = sourceMapError(this, {})