Skip to content

CLI: Categorize Execa failures for init telemetry - #35720

Merged
ndelangen merged 9 commits into
nextfrom
categorize-execa-errors
Aug 4, 2026
Merged

ndelangen merged 9 commits into
nextfrom
categorize-execa-errors

Conversation

@ndelangen

@ndelangen ndelangen commented Aug 3, 2026 •

Copy link
Copy Markdown
Member

Related: #34301

What I did

Init and addon setup failures were reaching telemetry as raw Execa errors, which collapsed into opaque errorHash buckets (including a large EMPTY_MESSAGE group). This wraps every executeCommand / executeCommandSync failure in categorized StorybookError subclasses so telemetry receives stable SB_* codes.

Added error classes for the high-volume failure modes seen in production data:

  • Generic Execa fallback (SB_CLI_0003)
  • Package install failures (SB_CLI_INIT_0010–0014) — dependency conflicts, missing manifest, pnpm ignored builds, pnpm no-TTY
  • Missing package manager binary (SB_CLI_INIT_0015)
  • Playwright browser install (SB_CLI_INIT_0016)
  • Nuxt nuxi module add (SB_CLI_INIT_0017)
  • Automigrate addon-a11y-addon-test (SB_CLI_AUTOMIGRATE_0003)

Errors always include a message rebuilt from Execa output (stderr/stdout/shortMessage) or, when those are empty, from the exit code — so telemetry no longer hashes blank strings.

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

No manual testing is required. This change only affects error classification and messaging for failed subprocess calls; behavior on success is unchanged. Maintainers can verify by running yarn test categorize-execa-error and inspecting that a failing storybook init reports an SB_* error code in telemetry.

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts
  • Declare whether manual QA will be needed for this PR during the next release, through qa:needed or qa:skip
  • Make sure this PR contains one of the labels below:

Made with Cursor

Wrap subprocess errors from executeCommand in StorybookError subclasses so telemetry can slice init failures by SB_* code instead of opaque message hashes, including stable messages when Execa output is empty.

Closes #34301

Co-authored-by: Cursor <cursoragent@cursor.com>
@ndelangen ndelangen added bug ci:normal Run our default set of CI jobs (choose this for most PRs). qa:skip Pull Requests that do not need any QA. (e.g. documentation) labels Aug 3, 2026
@ndelangen ndelangen self-assigned this Aug 3, 2026
@storybook-app-bot

storybook-app-bot Bot commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor

Package Benchmarks

Commit: 37304d6, ran on 4 August 2026 at 11:31:06 UTC

The following packages have significant changes to their size or dependencies:

storybook

Before After Difference
Dependency count 73 73 0
Self size 21.42 MB 21.43 MB 🚨 +13 KB 🚨
Dependency size 31.21 MB 31.21 MB 0 B
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 205 205 0
Self size 829 KB 829 KB 0 B
Dependency size 86.10 MB 86.12 MB 🚨 +13 KB 🚨
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 198 198 0
Self size 32 KB 32 KB 🚨 +36 B 🚨
Dependency size 84.58 MB 84.59 MB 🚨 +13 KB 🚨
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 74 74 0
Self size 1.09 MB 1.09 MB 0 B
Dependency size 52.63 MB 52.65 MB 🚨 +13 KB 🚨
Bundle Size Analyzer node node

ndelangen and others added 5 commits August 3, 2026 16:58
Keep only coverage for empty-output messaging, install classification, and the generic fallback — matching how other categorized errors are tested in the repo.

Co-authored-by: Cursor <cursoragent@cursor.com>
command.test.ts already covers categorized failures at the executeCommand boundary, consistent with how other Storybook errors are tested in this repo.

Co-authored-by: Cursor <cursoragent@cursor.com>
Returning execaProcess.catch(...) stripped ResultPromise methods like on('exit'), which broke storybook init in CI. Categorize failures via patched then/catch handlers instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ndelangen
ndelangen marked this pull request as ready for review August 3, 2026 15:43
@ndelangen
ndelangen requested review from a team, Copilot and shilman August 3, 2026 15:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves CLI/init telemetry by converting raw Execa failures into categorized StorybookError subclasses with stable SB_* error codes and non-empty messages derived from Execa output (stdout/stderr/shortMessage/exit code), addressing large opaque errorHash buckets (e.g. EMPTY_MESSAGE) reported in #34301.

Changes:

  • Added new CLI/init/automigrate StorybookError subclasses to represent common subprocess failure modes (install conflicts, missing manifest, pnpm-specific failures, missing binary, Playwright install, Nuxt module add, automigrate).
  • Introduced categorizeExecaError() to map Execa-like errors into these stable error classes based on command/args/log signatures.
  • Updated executeCommand / executeCommandSync to throw categorized StorybookErrors (and updated tests accordingly); ensured JsPackageManager rethrows Storybook-originated errors to preserve categorization.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
code/core/src/server-errors.ts Adds new StorybookError subclasses and shared data shape for categorized Execa failures.
code/core/src/common/utils/command.ts Wraps async/sync command execution failures with categorizeExecaError for stable error codes/messages.
code/core/src/common/utils/command.test.ts Updates expectations to assert categorized error types and preserves subprocess methods.
code/core/src/common/utils/categorize-execa-error.ts New categorization logic extracting logs/codes and mapping into the new error classes.
code/core/src/common/js-package-manager/JsPackageManager.ts Preserves categorized Storybook errors by rethrowing them instead of wrapping in HandledError.
Suppressed comments (1)

code/core/src/common/utils/command.ts:53

  • Similarly, the patched .catch passes onRejected directly into a new .catch(...). If a non-function is provided, native Promise behavior would ignore it, but this code will forward it and may throw at runtime. Guard it with a typeof === 'function' check.
  execaProcess.catch = ((onRejected) =>
    originalCatch((error) => {
      throw categorize(error);
    }).catch(onRejected)) as ResultPromise['catch'];

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread code/core/src/common/utils/categorize-execa-error.ts Outdated
Comment thread code/core/src/common/utils/command.ts
Comment thread code/core/src/common/utils/categorize-execa-error.ts
@coderabbitai

coderabbitai Bot commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3b7c2ded-705a-4b9b-9804-3c058ac11426

📥 Commits

Reviewing files that changed from the base of the PR and between 12093eb and 37304d6.

📒 Files selected for processing (1)
  • code/core/src/common/utils/categorize-execa-error.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/core/src/common/utils/categorize-execa-error.ts

Walkthrough

Changes

Execa error categorization

Layer / File(s) Summary
Error contracts and formatted details
code/core/src/server-errors.ts
Added shared Execa error data, formatting helpers, and specialized StorybookError subclasses.
Failure detection and categorization
code/core/src/common/utils/categorize-execa-error.ts
Added detection for package-manager, Playwright, Nuxt, automigration, missing-binary, manifest, dependency, and pnpm failures.
Command execution integration and validation
code/core/src/common/utils/command.ts, code/core/src/common/js-package-manager/JsPackageManager.ts, code/core/src/common/utils/command.test.ts
Categorized asynchronous and synchronous command failures, preserved fromStorybook errors, and updated command tests.

Sequence Diagram(s)

sequenceDiagram
  participant command.ts
  participant categorizeExecaError
  participant StorybookError
  command.ts->>categorizeExecaError: pass Execa failure metadata
  categorizeExecaError->>StorybookError: create specialized error with cause
  StorybookError-->>command.ts: return categorized failure
Loading

Possibly related issues

  • storybookjs/storybook issue 35072: The PR implements Execa failure categorization and specialized Storybook errors described by the issue.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
code/core/src/common/utils/command.ts (1)

32-54: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Patch .finally together with .then and .catch. Execa 9.6.1 binds all three methods to its internal promise, so execaProcess.finally(...) bypasses the patched handlers and can reject with the uncategorized error. No current executeCommand(...).finally(...) call sites exist, but ResultPromise exposes this API.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/core/src/common/utils/command.ts` around lines 32 - 54, Update
attachCategorizedExecaFailureHandler to also wrap execaProcess.finally,
preserving the existing categorization behavior when the underlying
ResultPromise rejects. Bind and delegate through the original finally
implementation, ensuring finally callbacks remain unchanged while errors
reaching the returned promise are categorized consistently with the patched then
and catch methods.
code/core/src/common/js-package-manager/JsPackageManager.ts (1)

380-399: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Await the runAddDeps() result inside this try.

All implementations return executeCommand()’s ResultPromise, while categorizeExecaError() runs only on rejection. Returning result lets addDependencies() adopt that rejection after this catch exits, so the fromStorybook branch cannot handle categorized failures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/core/src/common/js-package-manager/JsPackageManager.ts` around lines 380
- 399, Update the addDependencies try block around runAddDeps so it awaits the
returned ResultPromise before assigning or returning the result. Keep the await
inside the existing try, ensuring rejected executeCommand failures reach the
catch and preserve the fromStorybook handling and HandledError conversion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/core/src/common/utils/categorize-execa-error.ts`:
- Around line 113-121: Update isPackageInstall to treat the add argument as a
package-install command alongside install, preserving the existing command
normalization and package-manager checks.

In `@code/core/src/server-errors.ts`:
- Around line 943-979: Remove the duplicate ExecaCommandErrorData type and
formatExecaCommand/formatExecaFailureDetails helpers from
code/core/src/server-errors.ts:943-979, importing and reusing the single-source
definitions from code/core/src/common/utils/categorize-execa-error.ts instead.
Keep code/core/src/common/utils/categorize-execa-error.ts:21-60 as the canonical
implementation and make no direct changes there unless needed to support the
shared imports.

---

Nitpick comments:
In `@code/core/src/common/js-package-manager/JsPackageManager.ts`:
- Around line 380-399: Update the addDependencies try block around runAddDeps so
it awaits the returned ResultPromise before assigning or returning the result.
Keep the await inside the existing try, ensuring rejected executeCommand
failures reach the catch and preserve the fromStorybook handling and
HandledError conversion.

In `@code/core/src/common/utils/command.ts`:
- Around line 32-54: Update attachCategorizedExecaFailureHandler to also wrap
execaProcess.finally, preserving the existing categorization behavior when the
underlying ResultPromise rejects. Bind and delegate through the original finally
implementation, ensuring finally callbacks remain unchanged while errors
reaching the returned promise are categorized consistently with the patched then
and catch methods.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 81b1af85-7533-4831-ac07-f17198f5fb81

📥 Commits

Reviewing files that changed from the base of the PR and between bb5ab56 and 324d072.

📒 Files selected for processing (5)
  • code/core/src/common/js-package-manager/JsPackageManager.ts
  • code/core/src/common/utils/categorize-execa-error.ts
  • code/core/src/common/utils/command.test.ts
  • code/core/src/common/utils/command.ts
  • code/core/src/server-errors.ts

Comment thread code/core/src/common/utils/categorize-execa-error.ts Outdated
Comment thread code/core/src/server-errors.ts
Check command-specific failures before binary-not-found, treat package-manager add/ci as install failures, dedupe ExecaCommandErrorData helpers via server-errors exports, and only invoke promise rejection handlers when they are functions.

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/core/src/common/utils/categorize-execa-error.ts`:
- Around line 151-152: Update the binary-not-found branch in
categorizeExecaError so it returns PackageManagerBinaryNotFoundError only when
isBinaryNotFound(data) and data.command identifies a package-manager command;
allow missing non-package-manager executables to follow the existing alternative
classification path.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: edbf4938-b16e-4c35-afea-cd0e89e1b7a5

📥 Commits

Reviewing files that changed from the base of the PR and between 324d072 and 12093eb.

📒 Files selected for processing (3)
  • code/core/src/common/utils/categorize-execa-error.ts
  • code/core/src/common/utils/command.ts
  • code/core/src/server-errors.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • code/core/src/common/utils/command.ts
  • code/core/src/server-errors.ts

Comment thread code/core/src/common/utils/categorize-execa-error.ts Outdated

@shilman shilman left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Other than my one question below LGTM. Thanks for handling this!

Did you manually test that one of the categorized errors gets reported in practice when you force the error by setting STORYBOOK_TELEMETRY_DEBUG? If not, you should.

Comment thread code/core/src/common/js-package-manager/JsPackageManager.ts
@ndelangen

Copy link
Copy Markdown
Member Author

@shilman
I manually verified with STORYBOOK_TELEMETRY_DEBUG=1 on the compiled branch. Confirmed categorized errors appear in telemetry debug output with code, name, and category:

SB_CLI_INIT_0011 (PackageInstallDependencyConflictError) — forced via storybook init on a project with conflicting react/react-dom versions
SB_CLI_0003 (ExecaCommandFailedError) — forced via a failing npm subprocess through the same telemetry path

An agent set up the reproduction, but I saw the telemetry in the SB CLI firsthand.

ndelangen and others added 2 commits August 4, 2026 13:20
Only classify missing npm/pnpm/yarn/bun binaries as PackageManagerBinaryNotFoundError so other missing executables fall through to ExecaCommandFailedError.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ndelangen
ndelangen merged commit 002c4d9 into next Aug 4, 2026
136 checks passed
@ndelangen
ndelangen deleted the categorize-execa-errors branch August 4, 2026 12:58
@github-actions github-actions Bot mentioned this pull request Aug 4, 2026
2 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug ci:normal Run our default set of CI jobs (choose this for most PRs). cli qa:skip Pull Requests that do not need any QA. (e.g. documentation) telemetry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants