AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

Telemetry Request Ignores Built-In Timeout When Caller Signal Is Provided

When `next build` supplies a cancellation signal to the telemetry POST request, the built-in five-second timeout is not applied. This allows stalled telemetry endpoints to block the build for far longer than intended (e.g., 20-45 seconds) because only the caller's signal is used, which normally does not fire during a local build.

mediumConfidence 95%Next.jsAffected V16.2.6Affected V16.3.0-Canary.97

Origin Analysis

In `packages/next/src/telemetry/post-telemetry-payload.ts`, the code likely only sets a five-second timeout when no caller-provided `AbortSignal` exists. When a signal is passed (as done by the build process), that signal is passed directly to `fetch`, bypassing the timeout. The result is that the request falls back to Node.js/undici default timeouts, which are much longer.
1. Clone the reproduction repo: `git clone https://github.com/cyhforlight/next-telemetry-timeout-repro.git && cd next-telemetry-timeout-repro` 2. Build the Docker image: `docker build -t next-telemetry-timeout-repro .` 3. Run the reproduction with a mocked stalled telemetry endpoint: `docker run --rm --add-host telemetry.nextjs.org:127.0.0.1 next-telemetry-timeout-repro` 4. Observe that the telemetry-disabled build completes in ~2-3s, while the telemetry-enabled build takes significantly longer than the built-in five-second timeout.

Fixing Code Block

const controller = new AbortController() const timeout = setTimeout(() => controller.abort(), 5000) if (signal) { if (signal.aborted) { controller.abort() } else { signal.addEventListener('abort', () => controller.abort(), { once: true }) } } const response = await fetch(TELEMETRY_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), signal: controller.signal, })
Instead of conditionally installing the timeout only when no caller signal exists, always create an AbortController and a five-second timeout. Forward any caller-provided signal to that controller by listening for its 'abort' event. Then pass the controller's signal to fetch. This ensures both the built-in timeout and caller-initiated cancellation are honored.

Edge Case Audit

This change combines cancellation sources. If the caller signal aborts early (e.g., during a build cancellation), the abort listener will fire and abort the telemetry request immediately, which is intended. Ensure the timeout is cleared in a finally block to prevent a late timeout from aborting an already completed request. In concurrent builds, each telemetry call independently creates its own controller and timeout, so no cross-request interference is expected. Rollback suggestion: if unexpected behavior occurs, revert to the previous conditional timeout but keep the combined signal logic only for the caller-provided path; however, this would reintroduce the original bug. Monitor telemetry request durations to verify the timeout is effective.

Ecosystem Topology