-
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
Telemetry: Fix race condition in telemetry cache causing malformed JSON #33323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ons and error handling - Updated `set` function to queue multiple operations and process them sequentially. - Enhanced error handling to allow subsequent operations to succeed even if a previous one fails. - Modified `getLastEvents` to wait for the completion of queued operations before returning data. - Adjusted tests to cover new queuing behavior and error scenarios.
|
View your CI Pipeline Execution ↗ for commit bb01d69
☁️ Nx Cloud last updated this comment at |
📝 WalkthroughWalkthroughCentralizes telemetry imports, exposes getLastEvents/CacheEntry/getSessionId, converts event-cache to a queued async model (processingPromise) with typed set/getPrecedingUpgrade, and extends preview-initialized payload to accept and use a sessionId. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
✨ Finishing touches
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
code/core/src/telemetry/event-cache.ts (1)
97-103:getLastEventsmay throw if the last queuedsetoperation failed.Since
currentOperationcan be a rejected promise (when asetoperation fails), awaiting it without a try-catch will propagate that rejection to callers ofgetLastEvents. This may be unexpected behavior for a "read" operation.Consider catching the rejection to ensure
getLastEventsalways returns data:export const getLastEvents = async (): Promise<Record<EventType, CacheEntry>> => { // Wait for any pending set operations to complete before reading // This prevents race conditions where getLastEvents() reads stale data // while a set() operation is still in progress - await currentOperation; + try { + await currentOperation; + } catch { + // Previous operation failed, but we can still read + } return (await cache.get('lastEvents')) || {}; };
🧹 Nitpick comments (2)
code/core/src/core-server/server-channel/preview-initialized-channel.ts (1)
3-5: Consolidate duplicate imports from the same module.Lines 3, 4, and 5 all import from
'storybook/internal/telemetry'. These should be merged into a single import statement.-import { type InitPayload, telemetry } from 'storybook/internal/telemetry'; -import { type CacheEntry, getLastEvents } from 'storybook/internal/telemetry'; -import { getSessionId } from 'storybook/internal/telemetry'; +import { + type CacheEntry, + type InitPayload, + getLastEvents, + getSessionId, + telemetry, +} from 'storybook/internal/telemetry';code/core/src/telemetry/event-cache.test.ts (1)
287-289: ArbitrarysetTimeoutdelay may cause test flakiness.The 10ms delay is used to wait for queue processing to complete, but this approach is non-deterministic and could lead to flaky tests under load or in slower CI environments.
Consider using a more deterministic approach, such as exposing a way to await queue drain or increasing the timeout margin:
- // Wait a bit to ensure queue processing completes - await new Promise((resolve) => setTimeout(resolve, 10)); + // Wait a bit to ensure queue processing completes + await new Promise((resolve) => setTimeout(resolve, 50));Alternatively, if the implementation exposes a queue-drain mechanism, that would be preferable to arbitrary delays.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
code/core/src/core-server/server-channel/preview-initialized-channel.ts(1 hunks)code/core/src/telemetry/event-cache.test.ts(3 hunks)code/core/src/telemetry/event-cache.ts(2 hunks)code/core/src/telemetry/index.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{js,jsx,json,html,ts,tsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use ESLint and Prettier configurations that are enforced in the codebase
Files:
code/core/src/telemetry/index.tscode/core/src/telemetry/event-cache.tscode/core/src/telemetry/event-cache.test.tscode/core/src/core-server/server-channel/preview-initialized-channel.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Enable TypeScript strict mode
Files:
code/core/src/telemetry/index.tscode/core/src/telemetry/event-cache.tscode/core/src/telemetry/event-cache.test.tscode/core/src/core-server/server-channel/preview-initialized-channel.ts
code/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
code/**/*.{ts,tsx,js,jsx,mjs}: Use server-side logger from 'storybook/internal/node-logger' for Node.js code
Use client-side logger from 'storybook/internal/client-logger' for browser code
Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size
Files:
code/core/src/telemetry/index.tscode/core/src/telemetry/event-cache.tscode/core/src/telemetry/event-cache.test.tscode/core/src/core-server/server-channel/preview-initialized-channel.ts
code/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Export functions that need to be tested from their modules
Files:
code/core/src/telemetry/index.tscode/core/src/telemetry/event-cache.tscode/core/src/telemetry/event-cache.test.tscode/core/src/core-server/server-channel/preview-initialized-channel.ts
code/**/*.{js,jsx,json,html,ts,tsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
code/**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier with --write flag to format code before committing
Run ESLint with yarn lint:js:cmd to check for linting issues and fix errors before committing
Files:
code/core/src/telemetry/index.tscode/core/src/telemetry/event-cache.tscode/core/src/telemetry/event-cache.test.tscode/core/src/core-server/server-channel/preview-initialized-channel.ts
**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{test,spec}.{ts,tsx}: Test files should follow the naming pattern*.test.ts,*.test.tsx,*.spec.ts, or*.spec.tsx
Follow the spy mocking rules defined in.cursor/rules/spy-mocking.mdcfor consistent mocking patterns with Vitest
Files:
code/core/src/telemetry/event-cache.test.ts
**/*.test.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/spy-mocking.mdc)
**/*.test.{ts,tsx,js,jsx}: Usevi.mock()with thespy: trueoption for all package and file mocks in Vitest tests
Place all mocks at the top of the test file before any test cases
Usevi.mocked()to type and access the mocked functions in Vitest tests
Implement mock behaviors inbeforeEachblocks in Vitest tests
Mock all required dependencies that the test subject uses
Each mock implementation should return a Promise for async functions in Vitest
Mock implementations should match the expected return type of the original function
Mock all required properties and methods that the test subject uses in Vitest tests
Avoid direct function mocking withoutvi.mocked()in Vitest tests
Avoid mock implementations outside ofbeforeEachblocks in Vitest tests
Avoid mocking without thespy: trueoption in Vitest tests
Avoid inline mock implementations within test cases in Vitest tests
Avoid mocking only a subset of required dependencies in Vitest tests
Mock at the highest level of abstraction needed in Vitest tests
Keep mock implementations simple and focused in Vitest tests
Use type-safe mocking withvi.mocked()in Vitest tests
Document complex mock behaviors in Vitest tests
Group related mocks together in Vitest tests
Files:
code/core/src/telemetry/event-cache.test.ts
code/**/*.{test,spec}.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
code/**/*.{test,spec}.{ts,tsx,js,jsx}: Write meaningful unit tests that actually import and call the functions being tested
Mock external dependencies using vi.mock() for file system, loggers, and other external dependencies in tests
Aim for high test coverage of business logic (75%+ for statements/lines) using coverage reports
Files:
code/core/src/telemetry/event-cache.test.ts
🧠 Learnings (3)
📚 Learning: 2025-11-28T14:50:24.889Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-28T14:50:24.889Z
Learning: Applies to code/**/*.{test,spec}.{ts,tsx,js,jsx} : Mock external dependencies using vi.mock() for file system, loggers, and other external dependencies in tests
Applied to files:
code/core/src/telemetry/event-cache.test.ts
📚 Learning: 2025-11-24T17:49:59.279Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.279Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Each mock implementation should return a Promise for async functions in Vitest
Applied to files:
code/core/src/telemetry/event-cache.test.ts
📚 Learning: 2025-09-24T09:39:39.233Z
Learnt from: ndelangen
Repo: storybookjs/storybook PR: 32507
File: code/core/src/manager/globals/globals-module-info.ts:25-33
Timestamp: 2025-09-24T09:39:39.233Z
Learning: In Storybook, storybook/actions/decorator is a preview-only entrypoint and should not be included in manager globals configuration. The duplicatedKeys array in code/core/src/manager/globals/globals-module-info.ts is specifically for manager-side externalization, not preview entrypoints.
Applied to files:
code/core/src/core-server/server-channel/preview-initialized-channel.ts
🧬 Code graph analysis (2)
code/core/src/telemetry/event-cache.ts (1)
code/core/src/telemetry/types.ts (2)
EventType(7-42)TelemetryEvent(117-121)
code/core/src/telemetry/event-cache.test.ts (2)
code/core/src/telemetry/event-cache.ts (2)
set(58-90)getLastEvents(97-103)code/core/src/telemetry/index.ts (1)
getLastEvents(19-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: normal
- GitHub Check: Core Unit Tests, windows-latest
- GitHub Check: nx
- GitHub Check: nx
- GitHub Check: nx
- GitHub Check: nx
- GitHub Check: nx
🔇 Additional comments (9)
code/core/src/core-server/server-channel/preview-initialized-channel.ts (2)
8-25: LGTM!The
makePayloadfunction correctly guards against missing data with optional chaining and only computestimeSinceInitandisNewUserwhen session IDs match.
32-47: LGTM!The telemetry event handler correctly awaits async operations sequentially and guards against duplicate
preview-first-loadevents. The silent catch block is acceptable here since telemetry failures should not impact the user's workflow.code/core/src/telemetry/event-cache.ts (3)
17-26: LGTM!The
QueueItemtype and module-level state variables are well-structured for implementing a sequential processing queue.
34-56: LGTM!The
processQueuefunction correctly implements sequential processing with per-item error isolation. The guard pattern prevents concurrent execution, and the while loop ensures all queued items are processed.
58-90: LGTM!The
setfunction correctly implements queued operations. Theawait currentOperationensures callers wait appropriately, and the fire-and-forgetprocessQueue()call is safe becauseisProcessingprevents concurrent execution. The non-null assertions onresolveOperation!andrejectOperation!are safe since the Promise executor runs synchronously.code/core/src/telemetry/index.ts (1)
19-21: LGTM!The new exports (
getLastEvents,CacheEntry,getSessionId) correctly extend the public API surface, aligning with the centralized telemetry module pattern used throughout the codebase.code/core/src/telemetry/event-cache.test.ts (3)
6-8: LGTM!The import statement correctly references the updated exports, and the mock setup follows the coding guidelines with
vi.mock()using thespy: trueoption at the top of the file.
183-226: LGTM!The test correctly verifies that
getLastEventswaits for pendingsetoperations. Mock setup inbeforeEachand use ofvi.mocked()follow the coding guidelines.
228-268: LGTM!This test comprehensively validates the sequential processing of queued operations. The mock data progression and call count assertions correctly verify the expected behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
code/core/src/telemetry/event-cache.ts (1)
25-34: Excellent implementation of the queuing mechanism.The sequential processing pattern correctly ensures:
- Operations are serialized via promise chaining
- A failing operation doesn't block subsequent queued operations (via the caught chain on line 31)
- Callers still receive rejections for their specific operations (via the returned
runpromise)This aligns perfectly with the PR objectives.
Optional: Consider observability for queue errors.
The empty catch block on line 31 is intentionally minimal to keep the queue alive, but you might consider logging errors for debugging purposes. Based on coding guidelines, this would need the appropriate logger (server-side or client-side depending on context):
processingPromise = run.catch((error) => { // Log error for observability (use appropriate logger for context) // Note: caller still receives the rejection via returned `run` promise });This is entirely optional and depends on your observability requirements.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
code/core/src/telemetry/event-cache.ts(3 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,json,html,ts,tsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use ESLint and Prettier configurations that are enforced in the codebase
Files:
code/core/src/telemetry/event-cache.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Enable TypeScript strict mode
Files:
code/core/src/telemetry/event-cache.ts
code/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
code/**/*.{ts,tsx,js,jsx,mjs}: Use server-side logger from 'storybook/internal/node-logger' for Node.js code
Use client-side logger from 'storybook/internal/client-logger' for browser code
Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size
Files:
code/core/src/telemetry/event-cache.ts
code/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Export functions that need to be tested from their modules
Files:
code/core/src/telemetry/event-cache.ts
code/**/*.{js,jsx,json,html,ts,tsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
code/**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier with --write flag to format code before committing
Run ESLint with yarn lint:js:cmd to check for linting issues and fix errors before committing
Files:
code/core/src/telemetry/event-cache.ts
🧬 Code graph analysis (1)
code/core/src/telemetry/event-cache.ts (1)
code/core/src/telemetry/types.ts (1)
EventType(7-42)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: normal
- GitHub Check: nx
- GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (4)
code/core/src/telemetry/event-cache.ts (4)
17-17: LGTM! Clear naming and proper initialization.The renamed
processingPromiseis more descriptive than the previous variable name, and initializing to a resolved promise ensures the first queued operation executes immediately.
41-45: Correct synchronization for read operations.Awaiting
processingPromisebefore reading ensures all queued write operations complete first, preventing reads of stale or partially-updated data. This is essential for the sequential processing model.
60-66: Good type refinements and proper use of type guards.The signature change to
Partial<Record<EventType, CacheEntry>>more accurately reflects that not all event types may have cached entries. The type guard on line 63 correctly narrows the type fromCacheEntry | undefinedtoCacheEntry, which is TypeScript best practice.
68-72: Approve the optional parameter addition.The optional
eventsparameter is a good optimization that allows callers to pass pre-fetched event data and avoid redundant cache reads.The type cast on line 72 assumes the cache contains data matching the expected type structure. Since this module controls all writes to the cache via
setHelper(which enforces the correct type), the cast should be safe in practice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
code/core/src/telemetry/event-cache.test.ts (1)
6-8: Typed helper + fixtures look good; minor naming nit forerrorfixtureThe
createTelemetryEventhelper andCacheEntry-typed fixtures are a nice improvement: they keep sessionId/context/payload consistent and ensure snapshots track the actualTelemetryEventshape correctly. One small nit: theerrorfixture usescreateTelemetryEvent('build', 'error'), which is a bit confusing given the variable name and later use as an “error” event; ifTelemetryEvent['eventType']allows'error', consider switching tocreateTelemetryEvent('error', 'error')(or adding an inline comment) for clarity.Also applies to: 17-55, 175-193
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
code/core/src/telemetry/event-cache.test.ts(9 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{test,spec}.{ts,tsx}: Test files should follow the naming pattern*.test.ts,*.test.tsx,*.spec.ts, or*.spec.tsx
Follow the spy mocking rules defined in.cursor/rules/spy-mocking.mdcfor consistent mocking patterns with Vitest
Files:
code/core/src/telemetry/event-cache.test.ts
**/*.test.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/spy-mocking.mdc)
**/*.test.{ts,tsx,js,jsx}: Usevi.mock()with thespy: trueoption for all package and file mocks in Vitest tests
Place all mocks at the top of the test file before any test cases
Usevi.mocked()to type and access the mocked functions in Vitest tests
Implement mock behaviors inbeforeEachblocks in Vitest tests
Mock all required dependencies that the test subject uses
Each mock implementation should return a Promise for async functions in Vitest
Mock implementations should match the expected return type of the original function
Mock all required properties and methods that the test subject uses in Vitest tests
Avoid direct function mocking withoutvi.mocked()in Vitest tests
Avoid mock implementations outside ofbeforeEachblocks in Vitest tests
Avoid mocking without thespy: trueoption in Vitest tests
Avoid inline mock implementations within test cases in Vitest tests
Avoid mocking only a subset of required dependencies in Vitest tests
Mock at the highest level of abstraction needed in Vitest tests
Keep mock implementations simple and focused in Vitest tests
Use type-safe mocking withvi.mocked()in Vitest tests
Document complex mock behaviors in Vitest tests
Group related mocks together in Vitest tests
Files:
code/core/src/telemetry/event-cache.test.ts
**/*.{js,jsx,json,html,ts,tsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use ESLint and Prettier configurations that are enforced in the codebase
Files:
code/core/src/telemetry/event-cache.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Enable TypeScript strict mode
Files:
code/core/src/telemetry/event-cache.test.ts
code/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
code/**/*.{ts,tsx,js,jsx,mjs}: Use server-side logger from 'storybook/internal/node-logger' for Node.js code
Use client-side logger from 'storybook/internal/client-logger' for browser code
Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size
Files:
code/core/src/telemetry/event-cache.test.ts
code/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Export functions that need to be tested from their modules
Files:
code/core/src/telemetry/event-cache.test.ts
code/**/*.{test,spec}.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
code/**/*.{test,spec}.{ts,tsx,js,jsx}: Write meaningful unit tests that actually import and call the functions being tested
Mock external dependencies using vi.mock() for file system, loggers, and other external dependencies in tests
Aim for high test coverage of business logic (75%+ for statements/lines) using coverage reports
Files:
code/core/src/telemetry/event-cache.test.ts
code/**/*.{js,jsx,json,html,ts,tsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
code/**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier with --write flag to format code before committing
Run ESLint with yarn lint:js:cmd to check for linting issues and fix errors before committing
Files:
code/core/src/telemetry/event-cache.test.ts
🧠 Learnings (1)
📚 Learning: 2025-11-24T17:49:59.279Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.279Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Each mock implementation should return a Promise for async functions in Vitest
Applied to files:
code/core/src/telemetry/event-cache.test.ts
🧬 Code graph analysis (1)
code/core/src/telemetry/event-cache.test.ts (2)
code/core/src/telemetry/types.ts (1)
TelemetryEvent(117-121)code/core/src/telemetry/event-cache.ts (4)
CacheEntry(12-15)getPrecedingUpgrade(68-83)set(25-34)getLastEvents(41-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: normal
- GitHub Check: nx
- GitHub Check: Core Unit Tests, windows-latest
…t-event Telemetry: Fix race condition in telemetry cache causing malformed JSON (cherry picked from commit 5e26703)
Closes #
What I did
We observed that the lastEvents cache file occasionally becomes malformed (e.g., ending in sequences like ...}89}}}), leading to parsing errors.
The root cause was identified in event-cache.ts. The previous implementation of the set function attempted to queue write operations using a shared operation promise. However, because the operation variable was updated after the await statement, rapid concurrent calls failed to chain correctly.
The Scenario (Old Behavior): If three write events ($A$ , $B$ , $C$ ) are triggered in quick succession:
The Fix (New Behavior): This PR refactors
setto chain promises synchronously using.then(). This ensures that the queue is constructed immediately when the function is called, regardless of when the execution actually occurs.Flow with this fix:
processingPromisebecomesprocessingPromiseis updated toprocessingPromiseis updated toThis guarantees strictly sequential file I/O: A must finish before B starts, and B must finish before C starts.
Checklist for Contributors
Testing
The changes in this PR are covered in the following automated tests:
Manual testing
This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!
Documentation
MIGRATION.MD
Checklist for Maintainers
When this PR is ready for testing, make sure to add
ci:normal,ci:mergedorci:dailyGH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found incode/lib/cli-storybook/src/sandbox-templates.tsMake sure this PR contains one of the labels below:
Available labels
bug: Internal changes that fixes incorrect behavior.maintenance: User-facing maintenance tasks.dependencies: Upgrading (sometimes downgrading) dependencies.build: Internal-facing build tooling & test updates. Will not show up in release changelog.cleanup: Minor cleanup style change. Will not show up in release changelog.documentation: Documentation only changes. Will not show up in release changelog.feature request: Introducing a new feature.BREAKING CHANGE: Changes that break compatibility in some way with current major version.other: Changes that don't fit in the above categories.🦋 Canary release
This pull request has been released as version
0.0.0-pr-33323-sha-bb01d69f. Try it out in a new sandbox by runningnpx [email protected] sandboxor in an existing project withnpx [email protected] upgrade.More information
0.0.0-pr-33323-sha-bb01d69fvalentin/fix-telemetry-last-eventbb01d69f1765440706)To request a new release of this pull request, mention the
@storybookjs/coreteam.core team members can create a new canary release here or locally with
gh workflow run --repo storybookjs/storybook publish.yml --field pr=33323Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.