Repository navigation
[PR] fix: stop the Codex/opencode WebSocket lanes from 413-ing long sessions before compression (#4804) - #4808
ranxianglei wants to merge 7 commits into
Conversation
- export ResponsesWsSession so the onMessage admission seam is testable directly (same seam as the issue's offline repro harness) - tests/issue4804-ws-admission-boundary.test.ts expresses the acceptance behavior: full replay above the old 100 MiB cap reaches dispatch; small previous_response_id continuation over the reconstructed cap reaches dispatch; checkpoint accounting retains a completed turn whose combined size crosses the old cap. Verified to fail on unfixed code with the exact production signatures (request_too_large 413, previous_response_not_found). Controls (small turns pass, foreign id refused 400) stay green.
…4804) The Responses WebSocket lanes (codex-responses-ws, responses-ws) measured the RECONSTRUCTED full history against a hardcoded 100 MiB cap before dispatch ran, so a long session whose processed outbound was far below the cap died with 413 request_too_large on the next turn even when the client sent only a few MiB of delta; checkpoint accounting shared the same cap and cleared checkpoints near it, stranding continuations with previous_response_not_found. #3202 made the HTTP budgets configurable but explicitly left the WS surface fixed at 100 MiB. - ws maxPayload, reconstructed-body admission, upstream frame/backpressure/ output guards and checkpoint accounting all resolve limitsReceiveBytes() (BILI_RECEIVE_BODY_BYTES > network.receiveBodyBytes > 5 GiB) - zero new config fields - one full serialization per turn: admission measures the same wire string the pipeline envelope pushes (was two) Behavior changes (old -> new, defaults): - WS wire frame cap 100 MiB -> 5 GiB - WS reconstructed-body admission cap 100 MiB -> 5 GiB - checkpoint retention cap 100 MiB -> 5 GiB (near-cap checkpoints survive) - 413 message names the live budget instead of a constant "100 MiB" Restore the old value with BILI_RECEIVE_BODY_BYTES=104857600 (this also sets the HTTP receive budget, as it always did).
…4804) The codec comment claimed codex "never sends previous_response_id", but the #4804 real-session capture shows another codex-family build sending previous_response_id with a tiny input tail as its normal operating mode. Scope the claim to the 0.147.0 capture it came from; both shapes are live production traffic.
(ai: vllm-qwen/qwen3.8-27b) 查重结论:不重复。#3202(HTTP 侧预算可配置化)是直接前驱,当时明确把 WS 面留在固定 100 MiB;本 PR 是把同一旋钮延伸到 WS 面,属延续而非并列重复。#3128(抬 cap)已被 #3202 关闭,#4788/#4792 是内存/延迟与存储保留话题,机制不同。开放 PR 中引用 #4804 的只有本 PR 一个,无并行修复。 标签:bug / codex / opencode / scope/wiring / p1(底层缺陷 #4804 是 WS 协议接线面的 p1 可用性缺陷)。 审核结论(独立重读 diff + 本地红绿复现)1. 是什么问题:Codex/opencode 的 WebSocket 车道在压缩还没跑之前,就拿「从 checkpoint 重建出的完整历史」去比一个写死的 100 MiB 上限——长会话下一轮直接 413 2. 什么原因导致的:三个独立字节预算(ws 帧 maxPayload、重建体准入、checkpoint 保留)全部硬编码 3. 影响范围与严重程度:p1—— 4. 怎么修复的:三个预算全部走既有接收预算旋钮( 5. 是否有回归问题:逐项审过,无意回归。
6. 是否破坏缓存:不涉及。 7. 是否改了设计方向:无转向——纯复用既有旋钮体系;B 是既定下一步。 8. 是否引入新配置:无(既有旋钮;要恢复旧行为设 测试有效性:真缝测试(直接驱动 CI 状态:head 判定:代码审核通过,止血红可合(CI 绿后由人合并——默认值变更类按 §7.4 本就 human-only);#4804 验收标准 1/5 待方案 B 第二 PR。 |
…help-merge sweep)
📦 Built Package ArtifactBranch: Latest build — install this exact versionnpm install -g billion-context@0.0.6292-pr.4808.8d88ec1This exact version is immutable and reproducible. The rolling Recent builds (commit → version)
Download artifact
tar xzf billion-context-pr4808.tgz
npm install -g packageInstall instructions are refreshed on each push. |
… declaration for lockfile-only churn kernel/package-lock.json is generated state, not kernel source. Agents' test prep (npm install inside kernel/, sweep-bot merges of stale lockfiles) churns it without any intent to change the kernel — 8 PRs (ranxianglei#4823 ranxianglei#4808 ranxianglei#4802 ranxianglei#4800 ranxianglei#4799 ranxianglei#4794 ranxianglei#4790 ranxianglei#4785) tripped the human gate on lockfile-only diffs, and the previous stopgap (bots appending kernel-change: declarations to PR bodies) was correctly rejected by the owner as bypassing the approval step. The human gate exists to protect kernel SOURCE. Rule 1 now ignores kernel/package-lock.json when deciding whether a PR 'touches kernel/'; real source/build-config changes still require the declaration + owner approval, and lockfile CONSISTENCY stays enforced by the separate lock-sync job (which fails on genuine drift — the ranxianglei#4725 merge red being today's live proof).
(ai: vllm-qwen/qwen3.8-27b) 冲突已解,新 head 关键发现(比冲突本身重要):本分支历史里带着两份旧 ci-fixes 快照合并(e8b7ae1 / 6253c19,#4599 help-merge sweep 遗留)。由于分支↔master↔ci-fixes 互相合并形成 criss-cross 拓扑,直接 八问:
本地验证:typecheck ✓、build ✓(dist-import-annotations / dist-external OK)、WS 车道定向 8 组 63/63 绿(issue4804×2、ws-bridge、responses-ws-http-fallback、codex-responses-ws、issue3701-ws-byte-budget、ws-upgrade、native-ws-intercept)。全量判定以推送后 CI 重跑为准。 结论:冲突解除,分支就绪,等 CI 绿灯即可,无需其他动作。 |
Part 1 (stopgap) of #4804 — see the in-thread plan; the root-cause fix (incremental trusted-prefix continuation path) follows as a second PR per the owner's pending decision and is what closes #4804 (acceptance criterion 1 requires it).
What was wrong
The Responses WebSocket lanes (
codex-responses-ws,responses-ws) measured the reconstructed full history against a hardcoded 100 MiB cap before dispatch ran (src/responses-ws.tsadmission). A long session whose processed outbound was far below the cap died with413 request_too_largeon the next turn even when the client sent only a few MiB of delta; checkpoint accounting shared the same cap and cleared checkpoints near it, stranding continuations withprevious_response_not_found. #3202 made the HTTP budgets configurable but explicitly left the WS surface fixed at 100 MiB.Fix
maxPayload, reconstructed-body admission, upstream frame/backpressure/output guards and checkpoint accounting all resolvelimitsReceiveBytes()(BILI_RECEIVE_BODY_BYTES>network.receiveBodyBytes> 5 GiB) — zero new config fields (existing knob, same as the HTTP lane since feat: configurable request-body byte budgets under network.* (#3128) #3202)Behavior changes (old → new, defaults) — disclosed per policy
maxPayload)limitsReceiveBytes())All guards remain explicit and bounded (per-connection single-flight + the #2076 backpressure pause); nothing becomes unbounded. Restore the old value with
BILI_RECEIVE_BODY_BYTES=104857600(this also sets the HTTP receive budget, as that knob always did).Tests
tests/issue4804-ws-admission-boundary.test.ts(first commit on this branch): drives the realResponsesWsSession.onMessage()seam — full-replay 101 MiB must reach dispatch; 99 MiB turn + ~2 MiB delta continuation must reach dispatch; near-boundary checkpoint must survive commit. All three reproduced the exact production refusal signature before this fix; all green after. Plus two control cases (small turns pass; unknownprevious_response_idstill refused 400).tests/issue4804-ws-budget-knob.test.ts(new): pinsBILI_RECEIVE_BODY_BYTES=1 MiBprocess-isolated; asserts admission AND checkpoint accounting honor the knob in both directions (Test Blind Spot Retrospective: Reinforcing Negative Assertion Discipline, Tightening Fake Upstream Strictness, Thin Inst | 测试盲区复盘:补反向断言纪律、假上游严格化、薄安装冒烟、形态矩阵与生命周期覆盖 #3617 negative assertions).codex-responses-ws(9/9) +request-body-budget+knobs-config-precedence; typecheck clean. Full-suite verdict = CI on this head.Wire/error codes unchanged (
request_too_large413,previous_response_not_found400); no config schema change; no kernel changes.