Telemetry: Add timeout to event-log POST to prevent build hang#35085
Conversation
A stalled connection to the telemetry endpoint could keep the Node event loop alive indefinitely, hanging `storybook build` after compilation. The post-build telemetry POST used `fetch` with no timeout/AbortSignal, so a connection that never settles was never abandoned, and the awaited call never returned. Time-box the request with `AbortSignal.timeout(30_000)` and switch `retryOn` to a function that stops retrying once the signal aborts (the array form treats the resulting AbortError as retryable, burning the backoff). 503/504 and transient network errors still retry. Closes storybookjs#35084
📝 WalkthroughWalkthroughThis PR adds explicit timeout handling to telemetry requests by introducing a 30-second abort signal and refactoring retry logic to respect abort conditions. The implementation prevents hanging requests by aborting on timeout and skipping retries for aborted requests. A new test validates the timeout behavior without hanging. ChangesTelemetry timeout and abort signal handling
Sequence DiagramsequenceDiagram
participant sendTelemetry
participant prepareRequest
participant retryingFetch
participant fetch
participant AbortSignal as AbortSignal.timeout()
sendTelemetry->>prepareRequest: initiate request
prepareRequest->>AbortSignal: create 30s timeout signal
prepareRequest->>retryingFetch: call with signal and maxRetries=3
loop Retry on network error or 503/504
retryingFetch->>fetch: call with abort signal
Note over fetch: fetch rejects on abort
fetch-->>retryingFetch: error or response
alt Request aborted
retryingFetch-->>sendTelemetry: stop retrying, settle promise
else Network error or 503/504 and attempt < maxRetries
retryingFetch->>fetch: retry
else Success or other error
retryingFetch-->>sendTelemetry: settle with result
end
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
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/telemetry/telemetry.test.ts`:
- Around line 40-77: Wrap this test in a describe block and move all mock
setup/behavior into a beforeEach: relocate the vi.spyOn(AbortSignal,
'timeout').mockReturnValue(...), fetchMock.mockImplementation(...) and any
related AbortController creation to the describe-level beforeEach so the test
body only performs invocation and assertions for sendTelemetry; keep the test
name and assertions unchanged and reference fetchMock, sendTelemetry,
AbortSignal.timeout, controller.abort and vi.waitFor when moving the setup so
the mock behavior is applied before each test.
🪄 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: 5646a024-583c-46f2-b51b-998373c09a98
📒 Files selected for processing (2)
code/core/src/telemetry/telemetry.test.tscode/core/src/telemetry/telemetry.ts
Package BenchmarksCommit: The following packages have significant changes to their size or dependencies:
|
| Before | After | Difference | |
|---|---|---|---|
| Dependency count | 72 | 74 | 🚨 +2 🚨 |
| Self size | 20.73 MB | 20.40 MB | 🎉 -333 KB 🎉 |
| Dependency size | 36.11 MB | 36.65 MB | 🚨 +539 KB 🚨 |
| Bundle Size Analyzer | Link | Link |
@storybook/cli
| Before | After | Difference | |
|---|---|---|---|
| Dependency count | 203 | 205 | 🚨 +2 🚨 |
| Self size | 908 KB | 908 KB | 🎉 -144 B 🎉 |
| Dependency size | 88.98 MB | 89.19 MB | 🚨 +205 KB 🚨 |
| Bundle Size Analyzer | Link | Link |
@storybook/codemod
| Before | After | Difference | |
|---|---|---|---|
| Dependency count | 196 | 198 | 🚨 +2 🚨 |
| Self size | 32 KB | 32 KB | 0 B |
| Dependency size | 87.47 MB | 87.67 MB | 🚨 +205 KB 🚨 |
| Bundle Size Analyzer | Link | Link |
create-storybook
| Before | After | Difference | |
|---|---|---|---|
| Dependency count | 73 | 75 | 🚨 +2 🚨 |
| Self size | 1.08 MB | 1.08 MB | 0 B |
| Dependency size | 56.84 MB | 57.05 MB | 🚨 +205 KB 🚨 |
| Bundle Size Analyzer | node | node |
Closes #35084 (and likely the root cause of #29828).
What I did
storybook buildcan hang after compiling: the post-build telemetry POST is afetchwith no timeout, so a stalled connection to the event-log endpoint never settles, the awaited call never returns, and the open socket keeps the event loop alive — the CLI never exits. (try/catchcan't catch a non-rejection;fetch-retryonly retries a settled attempt.)Time-box the request in
prepareRequest(core/src/telemetry/telemetry.ts):signal: AbortSignal.timeout(30_000), created once so it bounds the whole request.retryOnbecomes a function returningfalseoncesignal.aborted. Otherwisefetch-retrytreats theAbortErroras retryable and burns the 1s/2s/4s backoff on instant-aborting attempts (~37s, not 30s). The function also bounds attempts explicitly (attempt >= maxRetries), which the array form did implicitly.503/504 and transient errors still retry;
sendTelemetryalready swallows errors, so a timed-out event is just dropped — no user-visible change.Checklist for Contributors
Testing
The changes in this PR are covered in the following automated tests:
core/src/telemetry/telemetry.test.tsadds a regression test: a fetch that only settles once aborted is abandoned after the timeout and not retried (exactly one attempt). Existing 503/give-up tests still pass.Manual testing
The repro ships this exact change as a patch, so you can see before/after on the released build:
Documentation
Notes
AbortSignal.timeout()needs Node ≥17.3; Storybook requires Node ≥20.bug.Disclosure (per CONTRIBUTING.md): I used Claude heavily for the investigation, reproduction, and fix — all under my direction and review. Happy to go deeper on any of it.
Summary by CodeRabbit
Bug Fixes
Tests