kubernetes-client/javascript

Watch timeout signal lost when overridden with controller signal

Summary

  • Context: The Watch class establishes long-lived HTTP connections to Kubernetes API servers to monitor resource changes, using AbortSignal to handle both manual cancellation and automatic timeouts.

  • Bug: The timeout abort signal is overwritten immediately after being combined with the controller signal, causing the timeout mechanism to be lost.

  • Actual vs. expected: Watch operations never timeout when the server hangs or takes too long to respond, instead of aborting after the configured timeout period (default 30 seconds).

  • Impact: Watch operations can hang indefinitely when servers are unresponsive, preventing proper error handling and resource cleanup.

Code with bug

const requestInit = await this.config.applyToFetchOptions({});

const controller = new AbortController();
const timeoutSignal = AbortSignal.timeout(this.requestTimeoutMs);

requestInit.signal = AbortSignal.any([
  controller.signal,
  timeoutSignal,
]);

requestInit.signal = controller.signal as AbortSignal; 
// <-- BUG 🔴 This overwrites the combined signal, losing the timeout

requestInit.method = 'GET';

Evidence

The AbortSignal.any() call on line 44 correctly creates a combined signal that will abort when either the controller is manually aborted or the timeout expires. However, the assignment on the following line immediately overwrites this combined signal with only the controller signal, discarding the timeout functionality. As a result, the fetch request will only respond to manual cancellation via the controller, but will never timeout automatically, even if the server hangs indefinitely.

Recommended fix

Remove the line that overwrites the combined signal. Delete requestInit.signal = controller.signal as AbortSignal; to preserve the timeout functionality. // <– FIX 🟢