From f226e3db830a12617b214216fe08648410467c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20Str=C3=BCbing?= Date: Fri, 10 Jul 2026 14:10:59 +0000 Subject: [PATCH] fix: prevent premature stream closure in watch with undici 8.6.0 Remove body-level close/finish event listeners that could trigger controller.abort() before readline finished processing buffered data. With undici >= 8.6.0, this premature abort causes remaining response chunks (e.g. DELETED events) to be dropped. --- src/test/integration/watchPods.ts | 1 + src/watch.ts | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/test/integration/watchPods.ts b/src/test/integration/watchPods.ts index 3ede5f18467..dbe9c773b3f 100644 --- a/src/test/integration/watchPods.ts +++ b/src/test/integration/watchPods.ts @@ -53,6 +53,7 @@ export default async function watchPods() { pod.spec = { containers: [{ name: 'test', image: 'busybox', command: ['sleep', '3600'] }], restartPolicy: 'Never', + terminationGracePeriodSeconds: 0, }; await coreV1Client.createNamespacedPod({ namespace, body: pod }); diff --git a/src/watch.ts b/src/watch.ts index 85058117e2e..fb8d2e7cf1b 100644 --- a/src/watch.ts +++ b/src/watch.ts @@ -69,15 +69,15 @@ export class Watch { if (response.status === 200) { const body = Readable.fromWeb(response.body! as any); - - body.on('error', doneCallOnce); - body.on('close', () => doneCallOnce(null)); - body.on('finish', () => doneCallOnce(null)); - const lines = createInterface(body); + + // Only listen for completion/error on `lines`, not on `body`. + // Listening on `body` can trigger premature close before `lines` + // has finished processing buffered data, which causes + // controller.abort() to fire too early. With undici >= 8.6.0 + // this leads to remaining response chunks being dropped. lines.on('error', doneCallOnce); lines.on('close', () => doneCallOnce(null)); - lines.on('finish', () => doneCallOnce(null)); lines.on('line', (line) => { try { const data = JSON.parse(line.toString()); @@ -86,6 +86,8 @@ export class Watch { // ignore parse errors } }); + + body.on('error', doneCallOnce); } else { const statusText = response.statusText || STATUS_CODES[response.status] || 'Internal Server Error';