From cf43499dc96264542e69f726ede3bfee75221f2e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 2 Oct 2026 10:40:33 -0700 Subject: [PATCH] fix: preserve retained children after Bun output shutdown (#163633) Preserve retained descendants when Bun completes supervised command output. Keep the older-Bun FFI half-close, but do not treat Darwin's benign repeated-shutdown ENOTCONN as lost lineage; the host still independently requires output EOF and process extinction. The retained-child regression passes 3/3 on Node 24, Bun 86bd, and Bun 1e6f on macOS arm64 and Linux x64. Full applicable process/CUA and Mac-gate coverage, explicit desktop controls, P2 review, changed-file checks, and exact-head CI pass. Related: #163456 and openclaw/bun#76. --- docs/install/bun-compatibility.md | 2 +- src/process/supervisor/service-child-group-anchor.ts | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/install/bun-compatibility.md b/docs/install/bun-compatibility.md index 4e5328ce9ea9..982ea0537249 100644 --- a/docs/install/bun-compatibility.md +++ b/docs/install/bun-compatibility.md @@ -228,7 +228,7 @@ config or state. Gateway startup logs include the decision and its reason. ## Known limitations -- **Supervised command output:** Large piped responses, including CUA screenshots, preserve backpressure under Bun. Completed output reaches EOF while process cleanup retains authority, including on builds that retain duplicate standard-output descriptors. +- **Supervised command output:** Large piped responses, including CUA screenshots, preserve backpressure under Bun. Completed output reaches EOF while process cleanup retains authority, including on builds that retain duplicate standard-output descriptors. A runtime that already closed its output socket does not trigger descendant cleanup. - **Text boundaries:** OpenClaw works around a [JSC segment lookup bug](https://github.com/oven-sh/WebKit/pull/753) that can include the preceding cluster when a lookup starts on an emoji's high surrogate. Message chunking and terminal cells preserve the intended grapheme boundaries on Bun without runtime configuration changes. - **Desktop WebSockets:** OpenClaw uses the installed `ws` transport for desktop observers and paired-node desktop/portal streams. Bun 1.4.2's built-in `ws` server adapter lacks pause/resume and the Duplex stream bridge; the installed transport preserves backpressure, payload limits, and cleanup when a desktop disconnects. - **Lifecycle scripts:** Bun blocks dependency lifecycle scripts unless explicitly trusted with `bun pm trust`. diff --git a/src/process/supervisor/service-child-group-anchor.ts b/src/process/supervisor/service-child-group-anchor.ts index 724c8e98ee59..6f8b0c3ecd29 100644 --- a/src/process/supervisor/service-child-group-anchor.ts +++ b/src/process/supervisor/service-child-group-anchor.ts @@ -589,10 +589,9 @@ export function runServiceChildGroupAnchor(): void { }; for (const stream of ["stdout", "stderr"] as const) { pipeline(command[stream]!, process[stream], () => { - // Mirror Node's uv_shutdown until Bun stdio end() shuts down inherited socketpairs. - if (shutdownOutput?.(stream === "stdout" ? 1 : 2, 1) === -1) { - void requestCleanup("lineage-lost"); - } + // Older Bun needs this half-close; newer Bun can report ENOTCONN after its own. + // Shutdown completion is not lineage loss; the host still requires output EOF. + shutdownOutput?.(stream === "stdout" ? 1 : 2, 1); pendingOutput.delete(stream); void settleRoot(); });