Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions code/core/src/core-server/withTelemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,9 @@ export async function sendTelemetryError(
);

// If this is a StorybookError with sub-errors, send telemetry for each sub-error separately
if (error instanceof StorybookError && error.subErrors.length > 0) {
if (error && 'subErrors' in error && error.subErrors.length > 0) {
for (const subError of error.subErrors) {
if (subError instanceof StorybookError) {
await sendTelemetryError(subError, eventType, options, blocking, error);
}
await sendTelemetryError(subError, eventType, options, blocking, error as StorybookError);
}
}
Comment on lines +140 to 144
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add defensive checks to prevent runtime errors with non-StorybookError objects.

The change replaces strict type checking with duck-typing ('subErrors' in error), which introduces several type safety risks:

  1. No array validation: error.subErrors.length assumes subErrors is an array, but the in operator only checks property existence. If a non-array subErrors property exists, this will throw at runtime.

  2. Unsafe cast to StorybookError: Line 142 casts error as StorybookError without verification. When this reaches line 130, parent.fullErrorCode may be undefined if the error lacks this property.

  3. Missing structural validation: Each subError is passed recursively without verifying it matches the expected error structure.

While the try-catch block prevents crashes, it may mask legitimate issues and make debugging harder.

Consider adding defensive runtime checks:

-      if (error && 'subErrors' in error && error.subErrors.length > 0) {
+      if (
+        error &&
+        'subErrors' in error &&
+        Array.isArray(error.subErrors) &&
+        error.subErrors.length > 0 &&
+        'fullErrorCode' in error
+      ) {
         for (const subError of error.subErrors) {
           await sendTelemetryError(subError, eventType, options, blocking, error as StorybookError);
         }
       }

Alternatively, verify this change is intentional and document why duck-typing is preferred over instanceof StorybookError.

}
Expand Down
Loading