Skip to content

Build-wow: stop the status poller mislabelling real failures as timeouts#112986

Draft
borkweb wants to merge 1 commit into
trunkfrom
fix/build-wow-status-poller-followup
Draft

Build-wow: stop the status poller mislabelling real failures as timeouts#112986
borkweb wants to merge 1 commit into
trunkfrom
fix/build-wow-status-poller-followup

Conversation

@borkweb

@borkweb borkweb commented Jul 24, 2026

Copy link
Copy Markdown
Member

Part of BIGR-724.

Stacked on #112984 — this targets that branch so the diff shows only the follow-up. GitHub will retarget it to trunk automatically once #112984 merges. It should land after both #112984 and the paired backend PR, so the telemetry can be confirmed against a real endpoint before it is relied on.

Proposed Changes

Diagnostics and coverage follow-ups to the build-wow status poller. Nothing here changes what a user sees.

  • A timeout is only claimed when the request was actually aborted. The poller inferred it from controller.signal.aborted, which latches the instant the deadline fires and stays set. It now requires both the deadline to have passed and the rejection to be abort-shaped.
  • The reported reason keeps the HTTP status and is bounded before it reaches the logging pipeline.
  • describeRequestError recognises aborts. It was documented as handling a rejection that arrives as a DOM Event and could not — an Event has no message, so it fell through and returned the exact "[object Event]" the comment said it prevented.
  • Smaller hardening: response parsing moved below the request try so a malformed payload is not reported as a request failure; teardown clears both timers before calling abort(); onFailed sets state before logging.
  • Coverage: non-Error rejections, distinct statuses sharing a message, the deadline-passed-but-not-aborted path, an assertion that cancellation actually aborts the in-flight signal, plus hook-level unmount, request-failure wiring, and step-clamp tests.

Why are these changes being made?

wpcom-xhr-request has no signal support — only wpcom-proxy-request honours it, and client/lib/wp/browser.js selects the xhr transport for oauth and Jetpack-site builds. On those builds the abort is a no-op, so the request runs on to its real conclusion. Because signal.aborted was already latched, a genuine failure minutes later was recorded as request timed out after 15000ms and the actual cause was discarded. The fabricated reason also occupied one of the three deduplication slots, so the real error could be prevented from ever being reported. That is the opposite of what the failure telemetry was added for, and it degrades exactly when the endpoint is unhealthy.

The status was worth keeping for the same reason. Failures arrive as WPError, which carries the status separately from the message and whose message the response body can overwrite, so a 401 and a 403 sharing a body message collapsed into a single deduplication slot and one was silently dropped.

The coverage gaps are included because several of them would have kept the suite green with the behaviour removed — the test named for the "[object Event]" fix drove a hardcoded branch and never called the function it was named after, and the cancellation test asserted only a call count because the request had already settled by the time it ran.

Testing Instructions

  1. yarn test-client client/landing/stepper/declarative-flow/internals/steps-repository/site-generation/test — 37 tests pass.
  2. On a build using the xhr transport, stall a status request past the request deadline and then let it fail for a real reason. Confirm the logged build_wow_site_generation_status_request_failed reason is the real error with its HTTP status, not a timeout.
  3. Navigate away from the generation screen mid-build and confirm no request-failure event is emitted.

Pre-merge Checklist

Items from the template that do not apply to this change have been removed: there is no UI change (dark mode, accessibility), no new user-facing strings (string freeze, translations), and no selectors or expensive computations (memoizing).

  • Has the general commit checklist been followed? (PCYsg-hS-p2)
  • Have you written new tests for your changes? — 37 tests pass in site-generation/test. New coverage for non-Error rejections, distinct HTTP statuses sharing a message, the deadline-passed-but-not-aborted path, cancellation actually aborting the in-flight signal, and hook-level unmount / request-failure wiring / step-clamp. The clamp and the callback-liveness guards were each verified by removing them and confirming the new tests fail.
  • Have you checked for TypeScript, React or other console errors? — yarn typecheck-client reports 19 errors, all pre-existing on trunk and none in site-generation; yarn eslint is clean on the changed directory. Browser verification is pending the paired backend endpoint, since the failure paths this touches cannot be exercised until status requests can actually fail.
  • Have you tested the feature in Simple (P9HQHe-k8-p2), Atomic (P9HQHe-jW-p2), and self-hosted Jetpack sites (PCYsg-g6b-p2)? — build-wow is Atomic-only and gated to Automatticians, so Simple and self-hosted Jetpack do not apply. Atomic testing is blocked until the paired backend endpoint ships; until then the poll 404s and the changed code paths are unreachable.
  • Does this change what data we track or use (p4TIVU-aUh-p2)? — it changes the shape of an existing event rather than adding tracking, and does not affect Jetpack, so no privacy label. Worth a reviewer's eye regardless: build_wow_site_generation_status_request_failed now carries the HTTP status alongside a server-supplied error message, capped at 300 characters. The message originates from our own backend, not user input, and wp-error copies response-body fields onto the error object — so if that endpoint ever returns something sensitive in message, this would put it in Logstash.

Base automatically changed from fix/build-wow-status-poller to trunk July 24, 2026 23:07
## Summary

Diagnostics and coverage follow-ups to the build status poller. None of this changes what a user sees; it makes the failure telemetry trustworthy and closes test gaps where the suite would have stayed green with the behaviour removed.

## Why

The poller inferred "this request timed out" from `controller.signal.aborted`. That flag latches the instant the deadline fires and stays set for the rest of the iteration, so it only identifies the abort on transports that honour the signal. `wpcom-xhr-request` has no signal support at all, so on oauth and Jetpack-site builds the abort is a no-op, the request runs on to its real conclusion, and a genuine failure minutes later was recorded as `request timed out after 15000ms`. The actual cause was discarded — and because the fabricated reason occupied one of the three deduplication slots, it could prevent the real one from ever being reported.

The reason string also dropped the HTTP status. Failures arrive as `WPError`, which carries the status separately and whose message the response body can overwrite, so a 401 and a 403 sharing a body message collapsed to one deduplication slot and one of them was silently discarded.

`describeRequestError` was documented as the fix for the abort case rejecting with a DOM `Event`, which it could not do: an `Event` has no `message`, so it fell through to `String( error )` and returned the very `"[object Event]"` the comment said it prevented. The test named for that behaviour drove the hardcoded timeout branch instead and would have passed with the function deleted.

## How

A timeout is claimed only when the deadline has passed *and* the rejection is abort-shaped, via a new `isAbortLike()`; otherwise the real error is described. `describeRequestError` now recognises aborts, includes the HTTP status when present, and bounds the string before it reaches the logging pipeline.

Response parsing moves below the `try`, so a malformed payload from an injected fetcher is no longer reported as a request failure. Teardown clears both timers before calling `abort()`, which runs transport listeners synchronously at exactly the moment the page is going away. `onFailed` sets state before logging, since the poller dispatches terminal callbacks unguarded and a throw out of the log call would otherwise strand the user on a spinner until the deadline.

Coverage: `describeRequestError` gains cases for non-`Error` rejections and for distinct statuses sharing a message; the deadline-passed-but-not-aborted path is tested directly; the cancellation test is split so one case actually asserts the in-flight signal is aborted rather than only counting calls; and the hook gains unmount, `onRequestError` wiring, and step-clamp tests. The clamp and the callback-liveness guards were each verified by removing them and confirming the new tests fail.

## Testing

- [ ] `yarn test-client client/landing/stepper/declarative-flow/internals/steps-repository/site-generation/test` — 37 tests pass
- [ ] On a build using the xhr transport, stall a status request past the deadline and let it fail for a real reason; confirm the logged reason is the real error, not a timeout
@borkweb
borkweb force-pushed the fix/build-wow-status-poller-followup branch from 72605c5 to 767b623 Compare July 24, 2026 23:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant