From 03a43329772a012bbd18f86312a28d2051dfb2dc Mon Sep 17 00:00:00 2001 From: luvs01 Date: Fri, 7 Aug 2026 22:34:30 +0900 Subject: [PATCH] fix(stream): gate macOS eager relay on fixed Bun --- .../docs/ja/troubleshooting/windows-memory.md | 3 +-- .../docs/ko/troubleshooting/windows-memory.md | 2 +- .../docs/ru/troubleshooting/windows-memory.md | 10 +-------- .../docs/troubleshooting/windows-memory.md | 21 ++++++++++--------- .../zh-cn/troubleshooting/windows-memory.md | 2 +- src/lib/bun-stream-caps.ts | 15 ++++++------- src/types.ts | 4 ++-- structure/02_config-and-codex-home.md | 2 +- structure/04_transports-and-sidecars.md | 5 +++-- structure/05_gui-and-management-api.md | 2 +- tests/bun-stream-caps.test.ts | 8 +++++-- 11 files changed, 36 insertions(+), 38 deletions(-) diff --git a/docs-site/src/content/docs/ja/troubleshooting/windows-memory.md b/docs-site/src/content/docs/ja/troubleshooting/windows-memory.md index eca5b46fd1..81058bda73 100644 --- a/docs-site/src/content/docs/ja/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/ja/troubleshooting/windows-memory.md @@ -42,7 +42,6 @@ Windows では、#32111 クラッシュを回避するために、opencodex は 2. **`OPENCODEX_BUN_PATH` を使用して信頼できる Bun ランタイムを実行します。** これは 未検証の領域 — 私たちがテストしていないランタイムで opencodex を実行しています。自己責任で。サービスのインストールにとって重要: オーバーライドは、サービスの開始時ではなく、**サービス アーティファクトの生成時に**読み込まれます。環境変数を設定し、同じシェルから `ocx service repair` を再実行すると、パスが永続サービス定義に組み込まれます。 env を設定するだけでは、すでにインストールされているサービスには何も影響しません。 -3. **`streamMode: "eager-relay"` を使用して有界リレーにオプトインします。** 2 つの方法: -`config.json` を編集する (`"streamMode": "eager-relay"` を追加する) か、管理 API を呼び出します。`PUT /api/settings` と `{"streamMode":"eager-relay"}` は、再起動せずに新しいターンに適用されます。 **クラッシュのリスク警告:** Bun 1.3.14 では、#32111 の影響を受けるストリーム形状が使用されており、ストリームの途中でプロセスがクラッシュする可能性があります (Windows に限らず、どの OS でも)。サービス マネージャーはサービスを再起動しますが、実行中のリクエストは失敗します。 `"legacy-tee"` は現在のデフォルトを固定します。 Windows では、`"auto"` (デフォルト) によりランタイム ゲートが決定します。 macOS では、`"auto"` は常に T 上にあります。明示的な `"eager-relay"` はオプトインです。 +3. **`streamMode: "eager-relay"` で bounded relay を明示的に選択します。** 設定方法は変わりませんが、macOS では bundled runtime に #32111 の修正が含まれると検証された場合にのみ有効になり、それまでは tee を使用します。Windows では既存の明示設定の動作を維持します。`"legacy-tee"` は tee を固定し、`"auto"` は Windows では runtime gate に従い、macOS では常に tee を使用します。 これらのいずれかを実際の Windows ワークロードで試した場合は、[#314](https://github.com/lidge-jun/opencodex/issues/314) の `ocx doctor` メモリ セクションの前後を報告してください。これがまさにこの軽減策が待っている検証です。 diff --git a/docs-site/src/content/docs/ko/troubleshooting/windows-memory.md b/docs-site/src/content/docs/ko/troubleshooting/windows-memory.md index 4e53e3d5f3..e4ffdf638f 100644 --- a/docs-site/src/content/docs/ko/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/ko/troubleshooting/windows-memory.md @@ -36,6 +36,6 @@ Windows에서는 opencodex가 #32111 충돌을 피하기 위해 스트리밍 응 2. **`OPENCODEX_BUN_PATH`로 신뢰하는 Bun 런타임을 사용합니다.** 이 경로는 검증되지 않은 영역입니다. opencodex를 아직 테스트하지 않은 런타임에서 실행하는 것이므로, 위험은 사용자에게 있습니다. 서비스 설치에서 특히 중요한 점은 이 override가 서비스 시작 시가 아니라 **서비스 아티팩트를 생성할 때** 읽힌다는 것입니다. 환경 변수를 설정한 뒤, 같은 셸에서 `ocx service repair`를 다시 실행해야 경로가 영구적인 서비스 정의에 반영됩니다. 환경 변수만 설정하면 이미 설치된 서비스에는 아무 영향이 없습니다. -3. **`streamMode: "eager-relay"`로 bounded relay를 opt-in합니다.** 방법은 두 가지입니다. `config.json`을 수정해 `"streamMode": "eager-relay"`를 추가하거나, 관리 API에 `PUT /api/settings`와 `{"streamMode":"eager-relay"}`를 보내 새 턴에 재시작 없이 적용합니다. **충돌 위험 경고:** Bun 1.3.14에서는 이 방식이 #32111의 영향을 받는 스트림 형태를 사용하므로, 어떤 OS에서든 스트림 중간에 프로세스가 충돌할 수 있습니다. 서비스 관리자가 다시 시작하겠지만, 진행 중이던 요청은 실패합니다. `"legacy-tee"`는 현재 기본값을 고정합니다. Windows에서는 `"auto"`(기본값)가 런타임 게이트 판단을 따르게 합니다. macOS에서는 `"auto"`가 항상 tee를 유지하고, 명시적 `"eager-relay"`만 opt-in입니다. +3. **`streamMode: "eager-relay"`로 bounded relay를 명시적으로 선택합니다.** 설정 방법은 그대로지만 macOS에서는 bundled runtime에 #32111 수정이 포함되었다고 검증된 경우에만 적용되며, 그전에는 tee를 계속 사용합니다. Windows는 기존의 명시적 설정 동작을 유지합니다. `"legacy-tee"`는 tee를 고정하고, `"auto"`는 Windows에서 runtime gate를 따르며 macOS에서는 항상 tee를 사용합니다. 이 중 어떤 방법이든 실제 Windows 워크로드에 적용해 보셨다면, 변경 전후의 `ocx doctor` 메모리 섹션을 [#314](https://github.com/lidge-jun/opencodex/issues/314)에 남겨 주세요. 이것이 바로 이 완화책이 기다리고 있는 검증입니다. diff --git a/docs-site/src/content/docs/ru/troubleshooting/windows-memory.md b/docs-site/src/content/docs/ru/troubleshooting/windows-memory.md index 11fff3de88..3ff9da0af8 100644 --- a/docs-site/src/content/docs/ru/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/ru/troubleshooting/windows-memory.md @@ -77,15 +77,7 @@ opencodex поставляет рантайм Bun (сейчас это **1.3.14* оболочки, чтобы путь оказался зашит в долговременное определение службы. Одной только переменной для уже установленной службы недостаточно. -3. **Явно перейти на bounded relay через `streamMode: "eager-relay"`.** Есть два пути: - отредактировать `config.json` (добавить `"streamMode": "eager-relay"`) или вызвать management - API — `PUT /api/settings` с `{"streamMode":"eager-relay"}` применит изменение к новым ходам без - перезапуска. **Предупреждение о риске crash:** на Bun 1.3.14 это использует форму streaming, - затронутую #32111, и процесс может упасть прямо посреди потока (на любой ОС, не только на - Windows). Service manager перезапустит его, но все запросы в полёте потерпят неудачу. - `"legacy-tee"` жёстко фиксирует текущий путь по умолчанию. На Windows `"auto"` (по умолчанию) - позволяет рантайму выбрать путь самому. На macOS `"auto"` всегда остаётся на tee; явный - `"eager-relay"` — это opt-in. +3. **Явно перейти на bounded relay через `streamMode: "eager-relay"`.** Способы настройки не изменились, но на macOS этот режим включается только после подтверждения, что bundled runtime содержит исправление #32111; до этого macOS остаётся на tee. Windows сохраняет существующее поведение явной настройки. `"legacy-tee"` фиксирует tee, а `"auto"` использует runtime gate на Windows и всегда остаётся на tee на macOS. Если вы попробуете любой из этих вариантов на реальной Windows-нагрузке, пожалуйста, пришлите разделы памяти из `ocx doctor` до и после в diff --git a/docs-site/src/content/docs/troubleshooting/windows-memory.md b/docs-site/src/content/docs/troubleshooting/windows-memory.md index 54a9492d21..9efa3b2d06 100644 --- a/docs-site/src/content/docs/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/troubleshooting/windows-memory.md @@ -60,8 +60,9 @@ runtime the leak itself remains an upstream problem: - **A gated alternative stream path** — a bounded single-reader relay that removes the unbounded buffering shape entirely. On Windows it becomes the default automatically once a bundled Bun release verifiably carries the - #32111 fix; today it is opt-in only (see below). On macOS it stays opt-in - even after such a release — flipping macOS `auto` is a separate decision. + #32111 fix; today it is opt-in only (see below). On macOS it requires both + that verified runtime and explicit opt-in — flipping macOS `auto` is a + separate decision. Real-world RSS improvement from these changes is **awaiting verification by Windows users** — we do not claim the leak is fixed. @@ -74,8 +75,8 @@ restart it. 1. **Wait for a bundled runtime update.** Once a Bun release verifiably carries the fixes, opencodex will bump the bundled runtime and the safer - stream path turns on automatically on Windows (macOS keeps requiring the - explicit opt-in below). + stream path turns on automatically on Windows (macOS also keeps requiring + the explicit opt-in below). 2. **Run a Bun runtime you trust with `OPENCODEX_BUN_PATH`.** This is unvalidated territory — you are running opencodex on a runtime we have not @@ -88,12 +89,12 @@ restart it. 3. **Opt into the bounded relay with `streamMode: "eager-relay"`.** Two ways: edit `config.json` (add `"streamMode": "eager-relay"`), or call the management API — a `PUT /api/settings` with `{"streamMode":"eager-relay"}` - applies to new turns without a restart. **Crash risk warning:** on Bun - 1.3.14 this uses the stream shape affected by #32111, which can crash the - process mid-stream (on any OS, not just Windows). The service manager will - restart it, but in-flight requests fail. `"legacy-tee"` pins the current - default. On Windows, `"auto"` (default) lets the runtime gate decide. On - macOS, `"auto"` always stays on tee; explicit `"eager-relay"` is the opt-in. + applies to new turns without a restart. On Windows this explicit mode + retains its existing operator-controlled behavior. On macOS it is effective + only after the bundled runtime is verified to carry the #32111 fix; until + then macOS stays on tee. `"legacy-tee"` pins the current default, and + `"auto"` lets the Windows runtime gate decide while always staying on tee on + macOS. If you try any of these on a real Windows workload, please report the before and after `ocx doctor` memory sections on diff --git a/docs-site/src/content/docs/zh-cn/troubleshooting/windows-memory.md b/docs-site/src/content/docs/zh-cn/troubleshooting/windows-memory.md index 8d3ae224d8..c69b067e2d 100644 --- a/docs-site/src/content/docs/zh-cn/troubleshooting/windows-memory.md +++ b/docs-site/src/content/docs/zh-cn/troubleshooting/windows-memory.md @@ -36,6 +36,6 @@ opencodex 打包了 Bun 运行时(当前为 **1.3.14**)。这类内存增长 2. **通过 `OPENCODEX_BUN_PATH` 运行你信任的 Bun 运行时。** 这属于未验证区域,你是在一个我们没有测试过的运行时上运行 opencodex,风险自负。对服务安装而言,这个覆盖值是在生成服务产物时读取的,而不是在服务启动时读取的。先设置环境变量,然后在同一个 shell 中重新运行 `ocx service repair`,这样路径才会被写入持久化的服务定义。只设置环境变量对已经安装好的服务没有任何作用。 -3. **通过 `streamMode: "eager-relay"` 显式启用有界中继。** 有两种方式:编辑 `config.json`(添加 `"streamMode": "eager-relay"`),或调用管理 API - `PUT /api/settings` 携带 `{"streamMode":"eager-relay"}`,即可对新轮次生效,无需重启。**崩溃风险警告:** 在 Bun 1.3.14 上,这会使用受 #32111 影响的流形态,可能在流中途使进程崩溃(任何操作系统都会受影响,不只是 Windows)。服务管理器会把它重启,但正在进行的请求会失败。`"legacy-tee"` 会固定在当前默认路径。Windows 上,`"auto"`(默认值)会交给运行时门控决定。macOS 上,`"auto"` 始终保持 tee;显式 `"eager-relay"` 才是显式启用选项。 +3. **通过 `streamMode: "eager-relay"` 显式启用有界中继。** 配置方式不变,但在 macOS 上,只有当捆绑运行时已验证包含 #32111 修复时该设置才会生效;在此之前 macOS 会继续使用 tee。Windows 保留现有的显式配置行为。`"legacy-tee"` 固定当前默认路径;`"auto"` 在 Windows 上由运行时门控决定,在 macOS 上始终使用 tee。 如果你在真实的 Windows 工作负载上尝试这些方案,请把变更前后 `ocx doctor` 的内存部分发到 [#314](https://github.com/lidge-jun/opencodex/issues/314)——这正是这个缓解措施在等待的验证。 diff --git a/src/lib/bun-stream-caps.ts b/src/lib/bun-stream-caps.ts index 09395aab60..29f785b62b 100644 --- a/src/lib/bun-stream-caps.ts +++ b/src/lib/bun-stream-caps.ts @@ -7,10 +7,9 @@ * that fix yet, so `MIN_FIXED_BUN_VERSION` is null: every runtime is * "known-bad" until a bundle-bump commit sets it. Windows no-rewrite traffic * follows this runtime/config decision, preserving the explicit legacy-tee - * safety pin. Darwin no-rewrite traffic stays on tee - * for `auto` regardless of runtime capability and reaches eager relay only via - * explicit `streamMode: "eager-relay"` opt-in (see - * devlog/_fin/260731_macos_rss_retention/100_darwin_eager_optin.md). + * safety pin. Darwin no-rewrite traffic stays on tee for `auto` regardless of + * runtime capability and requires both explicit `streamMode: "eager-relay"` + * and a runtime proven to contain the fix. * * Prerelease conservatism: a version carrying a prerelease suffix (e.g. * `1.4.0-canary.3`) is NEVER treated as fixed even when its numeric triple @@ -91,8 +90,8 @@ export function decideEagerRelay( /** * Apply the two-platform eager-relay policy to the runtime/config capability. - * Windows preserves the decision for no-rewrite traffic. Darwin permits only - * explicit config opt-in; `auto` remains tee even on a future fixed runtime. + * Windows preserves the decision for no-rewrite traffic. Darwin requires an + * explicit config opt-in on a proven-fixed runtime; `auto` remains tee. * Returns the normalized effective decision, or null when platform policy, * rewrite needs, or a Darwin non-config-eager mode selects tee. */ @@ -109,7 +108,9 @@ export function selectEagerPath( const decision = decideEagerRelay(mode, version, minFixed); if (platform === "win32") return decision; - return decision.reason === "config-eager" ? decision : null; + return decision.reason === "config-eager" && bunHasAsyncPullCancelFix(version, minFixed) + ? decision + : null; } /** diff --git a/src/types.ts b/src/types.ts index 171628caff..d064f78ee5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -632,8 +632,8 @@ export interface OcxConfig { * Windows/macOS SSE passthrough stream shape (#314 mitigation). * On Windows, "auto" (default) selects eager relay only on a runtime proven * to carry the Bun#32111 fix. On macOS, "auto" always stays on legacy tee and - * eager relay is explicit-only. "eager-relay" opts into the new relay (and - * accepts #32111 crash risk on Bun 1.3.14); "legacy-tee" pins the tee path. + * eager relay requires both explicit opt-in and a runtime verified to carry + * the Bun#32111 fix. "legacy-tee" pins the tee path. * Persisted in config.json so service users can select the stream shape. * See src/lib/bun-stream-caps.ts. */ diff --git a/structure/02_config-and-codex-home.md b/structure/02_config-and-codex-home.md index 9610fdbb7f..b865fc92e9 100644 --- a/structure/02_config-and-codex-home.md +++ b/structure/02_config-and-codex-home.md @@ -80,7 +80,7 @@ matters for maintainers is which groups exist and who resolves them: | Routing | `defaultProvider`, `providers`, per-provider `selectedModels` | Explicit `provider/model` wins over `defaultProvider`. | | Catalog | `disabledModels`, `customModels`, `modelCacheTtlMs`, `providerContextCaps`, `contextCapValue` | Catalog state is derived; config only records intent. | | Retained state | `appOwnedMemoryBudgetMb` | Process-wide eviction target for app-owned logs, caches, blobs, and continuation payloads. Default 256 MiB, valid 64..4096; pinned state may temporarily exceed the target, but every pin-capable store has a finite local cap and their documented aggregate stays below `APP_OWNED_WORST_CASE_PINNED_BYTES` (512 MiB). Neither value caps RSS or native runtime memory. | -| Transport | stream mode, timeouts, proxy settings, `websockets` | `streamMode` persists in config.json; Windows services need a persisted input, and macOS uses it for explicit eager-relay opt-in. | +| Transport | stream mode, timeouts, proxy settings, `websockets` | `streamMode` persists in config.json; Windows services need a persisted input, and macOS requires a proven-fixed runtime in addition to explicit eager-relay opt-in. | | Credentials | `apiKeys` | Data-plane only; never admitted to `/api/*`. | | Lifecycle | `codexAutoStart`, shim/start behavior, resume-history sync, storage cleanup | Startup safety reads these; see [`05_gui-and-management-api.md`](05_gui-and-management-api.md). | diff --git a/structure/04_transports-and-sidecars.md b/structure/04_transports-and-sidecars.md index ed401de198..aa2c2724c4 100644 --- a/structure/04_transports-and-sidecars.md +++ b/structure/04_transports-and-sidecars.md @@ -49,8 +49,9 @@ Native passthrough SSE has TWO shapes, selected per request in `legacy-tee` and known-bad-runtime `auto` on tee as documented. When selected, `response.completed` closes the client stream even if upstream keeps HTTP/SSE alive. Darwin uses it for no-client-rewrite traffic only (neither image-gen - aliases nor item-id repair) and is explicit-only: `auto` stays tee even after - a future threshold bump. One eager reader + byte-bounded + aliases nor item-id repair), but requires both explicit config and a + proven-fixed runtime: `auto` stays tee even after a future threshold bump. + One eager reader + byte-bounded client queue + post-cancel bounded discard-drain replaces the tee and goes directly to the response without a JS rewrite wrapper, preserving the full inspection side-effect set (shared `createSseInspector` factory in `relay.ts`) diff --git a/structure/05_gui-and-management-api.md b/structure/05_gui-and-management-api.md index 4901a3c51e..d70e7dc101 100644 --- a/structure/05_gui-and-management-api.md +++ b/structure/05_gui-and-management-api.md @@ -79,7 +79,7 @@ this document owns is which module holds which area and what invariant that area | Endpoint area | Responsibility | | --- | --- | -| Config/settings | Read safe config/settings views; mutate supported settings only. Full `PUT /api/config` is disabled so masked secrets are not round-tripped. `PUT /api/settings` accepts `codexAutoStart`, `streamMode`, and/or integer `appOwnedMemoryBudgetMb` (64..4096; each optional, at least one required). Budget changes synchronously enforce the process-wide evictable retained-state cap; this is separate from RSS/native memory. `streamMode` persists the #314 stream-shape selection in config.json (Windows services need persisted input; macOS eager relay is explicit-only). | +| Config/settings | Read safe config/settings views; mutate supported settings only. Full `PUT /api/config` is disabled so masked secrets are not round-tripped. `PUT /api/settings` accepts `codexAutoStart`, `streamMode`, and/or integer `appOwnedMemoryBudgetMb` (64..4096; each optional, at least one required). Budget changes synchronously enforce the process-wide evictable retained-state cap; this is separate from RSS/native memory. `streamMode` persists the #314 stream-shape selection in config.json (Windows services need persisted input; macOS eager relay requires explicit config and a proven-fixed runtime). | | Startup safety | `GET /api/startup-health` reports whether injected Codex routing is restart-safe, with secret-free service/shim diagnostics. `POST /api/startup-action` provides allowlisted one-click installation for the background service or launcher shim. On Windows a healthy script shim is CLI-only; Codex Desktop requires the background service for full protection. | | Windows tray | `GET/POST /api/windows-tray` controls an owned, per-user HKCU login tray. The tray delegates fixed actions to the CLI and is never a proxy supervisor or restart-protection signal. | | Updates | `GET /api/update/check`, `POST /api/update/run`, and `GET /api/update/status` own dashboard self-update state. A launched worker PID is persisted in `update-job.json`; dead PIDs recover immediately, while legacy active records without a PID recover only after ten minutes. Live PIDs remain exclusive regardless of record age. `GET /api/update/badge` backs the sidebar badge: it reports that an update exists and links to the update surface rather than gating other actions. | diff --git a/tests/bun-stream-caps.test.ts b/tests/bun-stream-caps.test.ts index 2a7ad0b4cd..af53d496a5 100644 --- a/tests/bun-stream-caps.test.ts +++ b/tests/bun-stream-caps.test.ts @@ -113,8 +113,12 @@ describe("selectEagerPath (platform policy matrix)", () => { .toEqual({ useEagerRelay: true, reason: "auto-fixed-runtime" }); }); - test("darwin + no rewrite + config-eager → eager", () => { - expect(selectEagerPath("darwin", false, "eager-relay", "1.3.14", null)) + test("darwin + no rewrite + config-eager + known-bad runtime → tee", () => { + expect(selectEagerPath("darwin", false, "eager-relay", "1.3.14", null)).toBeNull(); + }); + + test("darwin + no rewrite + config-eager + fixed runtime → eager", () => { + expect(selectEagerPath("darwin", false, "eager-relay", "1.4.0", "1.4.0")) .toEqual({ useEagerRelay: true, reason: "config-eager" }); });