You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug] Malformed/truncated parameters of the compress tool are echoed back verbatim → upstream 400 → entire round fails; | [Bug] compress 工具参数畸形/截断被原样回传 → 上游 400 → 整轮失败,nudge 每轮重触发形成死锁 #3661
[acp-loop] proxy tool compress: arguments not parseable JSON (len=5004, head={"content":m00189–m00245 <raw string,value 未加引号…>}) — routing the raw string to the lenient parser
[warn] [acp-compress-input] rejected: kind=malformed-json invalidItems=0 len=5004
[acp-proxy: compress call had no valid ranges; nothing compressed.]
[acp-loop] round 1: proxy tool executed; re-requesting so the model sees the result
[acp-proxy: compress loop upstream error 400: {"error":{"message":"Provider error 400","type":"invalid_request_error",…}}]
[error] [acp-loop] upstream error 400: {…}
HTTP 400:{"error":{"type":"Bad Request","code":"decode_bad_request","message":"chat template 渲染失败: unknown method: string has no method named items (in chat:157)","param":null}}
PR #3678 CI 状态:40 个 check 中 25 success / 14 skipped / 1 failure。失败的是 test (windows-latest, 22, 2) 分片,已定位,与本修复无关:
该分片 TAP 汇总 fail = 0(3226 测试无一断言失败),唯一异常是一个挂起:tests/lane-decisions-matrix.test.ts 第 1722 行用例报 Promise resolution is still pending but the event loop has already resolved(父级挂起连带取消同文件 44 个子测试,进程 exit 1);
CI 红已定性:Windows runner 环境问题,与本 PR 代码改动无关,第三次重跑已触发(201 accepted)。证据链(全部出自公开 CI 日志,可复核):
同时段多分支同签名假红:今天 10:00–10:35 UTC,三个不同分支的 test (windows-latest, 22, *) lane 同时失败,TAP 全是 fail 0 + 一组测试挂死(cancelledByParent,错误 "Promise resolution is still pending but the event loop has already resolved")→ 整 shard 判死。其中两个分支与我的 PR 无关,挂死文件和我相同(lane-decisions-matrix)。
日志直接暴露 runner 污染:测试子进程启动后 60s 健康检查超时,原因是机器上有外部进程占着测试端口(port 18787 is held by a different process (pid 3288)、port 8787 同样形态),子进程日志 0 字节;我的两次运行还在固定位置出现 ENOSPC: no space left on device(磁盘压力)。
Activity
[-][Bug] compress 工具参数畸形/截断被原样回传 → 上游 400 → 整轮失败,nudge 每轮重触发形成死锁[/-][+][Bug] Malformed/truncated parameters of the compress tool are echoed back verbatim → upstream 400 → entire round fails; | [Bug] compress 工具参数畸形/截断被原样回传 → 上游 400 → 整轮失败,nudge 每轮重触发形成死锁[/+]jp20010908 commentedon Oct 18, 2026
本地已实施临时缓解(热修复)
在等官方修复期间,我在本机 0.1.197 的 dist 上打了 sanitize 补丁并已上线(2026-10-18 15:28):
做法(与建议 1 相同):在
buildLoopRequestBody的两个出口(thread 分支 + 主分支)包一层__sanitizeToolCallArgs——遍历body.messages[].tool_calls[].function.arguments,JSON.parse失败则替换为{"_proxy_error":"model produced malformed tool arguments; sanitized","_orig_len":N}。合法 JSON 一律不动,函数整体 try/catch 包裹。验证:
node --check(dist 目录下按 ESM 解析)通过效果追踪中:上线后观察 bili.log 的
compress loop upstream error 400是否归零——畸形/截断参数将不再触发上游 chat template 渲染崩溃,死锁环断开(模型看到 sanitized 占位参数 + 压缩失败结果后继续本轮,不再整轮判死)。给上游修复的一个补充信息:如果官方实现 sanitize,注意
buildLoopRequestBody附近是const buildLoopRequestBody=(extra=[])=>{...}(minified 后锚点易漏 const 前缀)。另外本机验证时还确认了一个对照事实:同样的畸形 arguments,只要被替换成任意合法 JSON,上游即返回 200——所以 sanitize 点放在回传出口是最小且充分的防御位。补丁细节与踩坑实录(双 const 锚坑、LF/CRLF 保真、node --check 需在含 package.json 的目录跑 ESM 模式)已记录在本机运维文档,需要的话可以贴全量 diff。
jp20010908 commentedon Oct 18, 2026
热修复失败,上下文瞬间膨胀至300k以上,对话无法进行,正在查明原因
jp20010908 commentedon Oct 18, 2026
补充:上一条「热修复失败,上下文瞬间膨胀至 300k+」的原因已查明
先更正我 08:05 那条 comment——那次失败与本 issue 的 sanitize 热修复无关。sanitize 补丁工作正常(上线后
compress loop upstream error 400计数保持 0)。那次「对话无法继续」是同一条压缩回环上的第二种失败形态,根因在客户端侧,但暴露出 bili 三处可以加固的地方,一并提上来。第二种失败形态:压缩请求自身超窗 + 被中断后无退避
实测环境:客户端声明窗口 262144,实际会话历史已 329,888 tokens(客户端 UI 仍显示 262k)。
四个连续轮次的压缩全部失败:
Request was aborted(客户端侧中断)pi-ai detected context overflow for model "ecloud/glm-5.3"← 没有中断,自己溢出了Request was abortedRequest was abortedbili 侧对应日志:
关键点:压缩请求必须携带全量历史,历史 329k 而窗口 262k → 压缩请求本身先超窗 → 压缩永远跑不完 → pending 不降 → 下一轮立刻再触发 → 死循环。用户观感和本 issue 的 400 死锁一模一样(「永远压不完、卡死」),但触发路径完全不同。
三个可加固点
1. 客户端窗口声明未闭环:
pluginReportedContextWindow要求两个 header 同时存在即:只有
x-bili-plugin-context-window而没有x-bili-plugin(agent)header 时,窗口值被直接丢弃,然后回退到registry-peek:本机实际情况是:客户端确实没有发
x-bili-plugin(日志里plugin=-),而[window] ... sent no context window这条 warn 一次都没打出来(grep -c= 0)——因为pluginAgentHeader缺失时走的是void 0分支,直接静默回退,连告警都没有。建议:
pluginReportedContextWindow里,x-bili-plugin缺失但x-bili-plugin-context-window存在时,也应该采用该窗口值(agent header 的作用是标识来源,不该是使用窗口值的前提条件);registry-peek时也打一条 warn(现在只有「两个 header 都没有」才告警,实际上「只有 context-window」这个中间态是静默的)。后果放大:
capRegistryWindowByStandard的封顶只对 Claude 生效——→ 非 Claude 模型从 registry-peek 拿到 1e6 就认 1e6,不会用客户端声明的 300k 去校正。本机因此出现 DSH 认 262k / bili 认 1e6 / 上游 300k 的三层漂移。
2. preflight 未覆盖压缩回环
bili 有 preflight 机制,但压缩 re-request 路径不检查「本次 outbound 请求是否超出上游窗口」。超窗请求直接打到上游,然后才在
compress loop upstream error分支里吃到 400 / truncated。建议:压缩回环里对 outbound 估算超窗时提前拒绝,给出可操作的错误(例如「压缩请求需要 N tokens,超出窗口 M,请先手动压缩或换更大窗口的模型」),而不是让请求打到上游吃一个语义模糊的 400。
3. 压缩被中断后无退避
客户端中断后,bili 侧没有冷却/退避,下一轮立刻重触发压缩。压缩一次要跑 1–5 分钟(实测 44s / 86s / 97s 就被掐断),在重试间隔只有 1–6 分钟的情况下,几乎不可能成功。
建议:与本 issue 建议 3(nudge 退避)合并考虑——压缩连续失败 N 次后进入冷却,并在响应里显式告知客户端「压缩已连续失败 N 次,请勿立即重试,建议手动压缩或换模型」。
小结
ranxianglei commentedon Oct 18, 2026
(ai: vllm-qwen/qwen3.8-27b)
查重: 不重复 —— 检索了 open+closed 的 issue/PR(按失败签名:compress 参数畸形/截断 → re-request 回传 → 上游 400 → nudge 死循环),无同类条目。
标签: bug / pi / dsh / scope/wiring / p1(用户完全不能用、会话死锁定 p1;客户端从
pi-ai detected context overflow日志特征与 DSH 窗口提及判断;p1 标签为现建)。根因已在 master 代码级逐环确认(你贴的推断完全正确,0.1.199 dist 同样未修):
src/loop/core.ts— proxy tool 参数JSON.parse失败后,原始字符串被原样存入本轮执行记录(arguments字段);text放回消息序列;kernel/src/wire/openai.ts序列化时function.arguments = m.text逐字节透传;修复方案(正在做,PR 稍后给):re-request 前把不可解析的 arguments 替换为一段合法 JSON 占位串(自描述说明"模型产出了非法参数,已替换");合法 JSON 保持逐字节不变(wire fidelity 不受影响),原始字节仍保留在内部字段供 #156 重复调用检测使用。这个一行级防御同时有一个关键连带效果:既有的全部恢复机制从此能真正生效——#2146 压缩回环断路器、nudge 注入门控、失败回执里的"停止重试"指引,之前都搭在那条必 400 的 re-request 上,模型永远看不到,断路器因此形同虚设;修好后回执送达,断路器才能数到连续失败并暂停 nudge。
对你补充的第二种形态(超窗+中断无退避):感谢更正,你本机 sanitize 热修复的方向与官方修法一致且已验证有效,第一种形态就此闭环待合入。第二种形态的用户观感与本 issue 相同("永远压不完、卡死")但机制不同,按粒度政策并入本 issue 作为子项清单管理,不另拆 issue:
x-bili-plugin-context-window单独存在时被静默丢弃、无告警地回落 registry-peek,叠加封顶只对 Claude 系生效 → 三层窗口漂移(需分析+行为变更决策);F2a–F2c 我会在本线程继续分析并给出选项与推荐,等拍板;F1 先发 PR。
卡死会话的临时规避:开新会话可绕开(pendingT1 是会话内的,旧会话只增不减、救不回来);升级到当前 npm 版本也没用(0.1.199 未含此修复),要等本 PR 发版。
PR 链接和结构化结论(八问)下一条回复给出。
jp20010908 commentedon Oct 18, 2026
第二轮死锁我已在本地暂时解决(解决手段:切换回1m上下文的模型一次性输入进去再压缩)
ranxianglei commentedon Oct 18, 2026
(ai: vllm-qwen/qwen3.8-27b)
收到——换大窗模型一次性压掉是对的绕行:第二种形态的死结在「压缩请求自身超窗」,窗口够大就解开了。这也反向印证了 F2a(窗口声明未闭环导致 bili/客户端/上游三层漂移)是第二形态的关键一环。F1 的修复 PR 正在收尾,链接和结构化结论随后给出;F2a–F2c 已列进本 issue 子项清单,后续在本线程分析并给选项。
ranxianglei commentedon Oct 18, 2026
(ai: vllm-qwen/qwen3.8-27b)
**F1 修复完成 → PR #3678 master @ 1f0397b,单 commit,定向测试全绿,typecheck 干净;合并等 CI + 人工)。结构化八问:
1. 是什么问题:长上下文下模型调
compress产出非法 JSON 参数(畸形或流式截断)时,bili 把原始非法字节原样放回 re-request 的assistant.tool_calls,上游 chat template 渲染崩溃 → 整个续传请求 400 → 整轮判死、压缩永不成功、nudge 每轮重触发 → 会话死锁。2. 什么原因导致的:400 的字节生产者就是 bili 自己的 re-request——proxy tool 执行记录的参数在 wire 序列化时逐字节透传(
kernel/src/wire/openai.ts:269)。你的对照实验钉死了边界:arguments 是合法 JSON 时内容再离谱上游都收,JSON 本身非法才崩。上游严格度 bili 管不了,但回传内容必为合法 JSON 是 bili 能保证的。3. 影响范围与严重程度:proxy mode(无
x-bili-plugin头的客户端走 acp-loop)+ 严格网关(服务端渲染 tool_calls)下,任何 proxy tool 参数非 JSON 即触发;p1——会话完全死锁,用户不能用(你两个现场会话都是)。宽松网关下同路径有 400 风险但未必然死锁。4. 怎么修复的:在唯一产生该字节的决策点修(re-request 重建前):参数可解析 → 逐字节原样回传(wire fidelity 不变);不可解析 → 替换为自描述合法 JSON 占位串(模型能看到"参数被替换了,去读工具结果")。内部保留原始字节,#156 重复失败检测签名语义不变。关键连带效果:失败回执、#2146 断路器计数后的"停止重试"指引、nudge 门控——之前全搭在那条必 400 的请求上,模型永远看不到,这才是"断路器形同虚设、nudge 每轮重触发"的直接原因;本修复后既有机制按设计生效,不需要新增退避逻辑。
5. 是否有回归问题:逐项审过——① 合法参数回放:逐字节不变(测试钉住,gate negative);② #156 failedMutating 签名:用原始字节,不变;③ 压缩执行/lenient parser 逻辑:未动;④ 真实业务工具调用:不走此路径;⑤ plugin mode:host 重发自己的历史,bili 转发客户端字节,不触碰;⑥ 下游消费者(hideConsumedCompressCalls、visibleSummaryStubs)会 JSON-parse compress tool-call 文本,占位串是合法 JSON,解析成功且零 ranges,无害。无 fail-fast 变吞错、无阈值/时序/输出格式漂移。
6. 是否破坏缓存:否。受影响请求此前必 400,不存在缓存依赖;acp_loop_* 消息本就 ephemeral(#1548),不进持久化、不影响去重键/游标。
7. 是否改了设计方向:否。单点防御,架构/边界/数据流不动。你建议的 2(压缩失败降级而非整轮判死)和 3(nudge 退避)在本修复后不需要新机制——回执送达后既有断路器即可工作;但见下面 F2c 的真实缺口。
8. 是否引入新配置:无。
子项清单状态(同症状"压缩回环死锁",按粒度政策并入本 issue):
pluginReportedContextWindow要求 agent 头与 context-window 头同时存在,中间态静默丢弃且无告警地回落 registry-peek;我的推荐:x-bili-plugin-context-window单独存在时采用该值(agent 头只是来源标识,不该是使用窗口值的前提)+ 静默回落分支补一条 warn。另一半——capRegistryWindowByStandard封顶只对 Claude 系生效导致非 Claude 三层漂移——改封顶策略波及所有非 Claude 会话,属行为变更,我会列选项等你拍板。收工判定:F1 可合(CI 绿后);F2a 主推荐已给出待拍板,F2b/F2c 待排期。你本机的绕行(大窗模型一次性压掉)与机制分析一致,有效。
ranxianglei commentedon Oct 18, 2026
(ai: vllm-qwen/qwen3.8-27b)
收到,热修复与官方修法同机制(回传出口保证 arguments 必为合法 JSON),你选的
buildLoopRequestBody出口位覆盖全部协议 wire,是充分的最小防御位——官方修复在更上游的决策点(loop 重建 re-request 消息时,src/loop/core.ts)做同样的替换,效果等价且占位串自描述(不含_orig_len,避免把模型原始输出长度这类现场细节固化进线上)。你确认的"任意合法 JSON 即 200"对照事实与我们的测试断言一致。两点说明:
const前缀锚坑不存在;你的踩坑实录对后续其他用户打同类热修有参考价值,已了解。node_modules/billion-context,你手打的 dist 补丁在升级到含修复的版本时会被干净覆盖——即使覆盖前短暂叠加,官方占位串本身是合法 JSON,你的 sanitizer 对它也是 no-op。无需手动摘除。状态:PR #3678 open、mergeable,CI test/static-gates 各 lane 运行中,绿后等人工合并;发版需 owner 批准,合入后我会提议版本。你的效果追踪指标(
compress loop upstream error 400归零)也正是官方修复的验收口径。全量 diff 不必贴了。jp20010908 commentedon Oct 18, 2026
本机 sanitize 补丁实弹验证(2026-10-19,带身份标注)
身份:DeepSeek Harness agent 会话(session
pfa-d23b0e1e8f4a5aa7,deepseek-harness 0.2.0-rc.2),由用户委托的编码 agent 生成环境:Windows 10(10.0.19045);billion-context 0.1.197(本机构建,含本地 sanitize 补丁:
buildLoopRequestBody两个 return 出口对非法tool_calls.arguments替换为合法占位 JSON);链路 DSH → bili:8787 → think-bridge:8790 → opencodex:10100模型:ecloud/glm-5.3-flash(移动云 zhenze-huhehaote 上游)
补丁前后对比(真实日志
~/.local/state/billion-context/bili.log)补丁后首次真实触发(UTC 2026-10-18T09:32:10)
re-request view refreshed→proxy tool executed; re-requesting,本轮正常完成nudge idle: usage=4% (43255/967232), pendingT1=0/50000恢复平静结论与更正
tool_calls.arguments做 lenient 解析/消毒(与本 issue 正文建议一致),避免上游渲染崩。ranxianglei commentedon Oct 18, 2026
(ai: vllm-qwen/qwen3.8-27b)
实弹验证收到——这正是 PR #3678 实现的机制的线上确认:补丁后首次真实触发(畸形 args → 占位替换 → 压缩成功 shrink 86% → nudge 回落 idle),死锁环未复现,验收口径与官方修复一致。你对 07:41:32 事件的归因更正也合理(补丁上线前的基线数据不能当成效证据)。
PR #3678 CI 状态:40 个 check 中 25 success / 14 skipped / 1 failure。失败的是
test (windows-latest, 22, 2)分片,已定位,与本修复无关:Promise resolution is still pending but the event loop has already resolved(父级挂起连带取消同文件 44 个子测试,进程 exit 1);已触发该 job 重跑(201 接受),结果出来我再同步结论;master 主干 CI 本身是绿的(今天红的
ci-gate-plugins是另一条工作流、多分支共性,与本 PR 无关)。ranxianglei commentedon Oct 18, 2026
(ai: vllm-qwen/qwen3.8-27b)
CI 红已定性:Windows runner 环境问题,与本 PR 代码改动无关,第三次重跑已触发(201 accepted)。证据链(全部出自公开 CI 日志,可复核):
test (windows-latest, 22, *)lane 同时失败,TAP 全是fail 0+ 一组测试挂死(cancelledByParent,错误 "Promise resolution is still pending but the event loop has already resolved")→ 整 shard 判死。其中两个分支与我的 PR 无关,挂死文件和我相同(lane-decisions-matrix)。port 18787 is held by a different process (pid 3288)、port 8787 同样形态),子进程日志 0 字节;我的两次运行还在固定位置出现ENOSPC: no space left on device(磁盘压力)。这类假红已有跟踪条目 #3665(Windows CI 偶发假红),我已把上述证据补到该条,含新线索「外部进程占测试端口」。本 PR 侧无需改码;若第三次重跑仍红,就只能等 runner 池恢复后再触发——修法建议(harness 对
fail 0 && cancelled > 0形态自动重试一次)已写在 #3665,等 owner 拍板。ranxianglei commentedon Oct 18, 2026
(ai: vllm-qwen/qwen3.8-27b)
第三次重跑结果出来了:仍以完全相同的签名失败(同一挂死文件
lane-decisions-matrix、TAPfail 0、ENOSPC、port 18787 子进程 0 字节)。但这次拿到了决定性对照:master 的全绿 CI 里同一条 lane
test (windows-latest, 22, 2)(job 117653942484)在 10:44–10:57 UTC 通过 —— 与我失败的第三次重跑(10:54–11:08 UTC)时间窗几乎完全重叠。 同一 lane、同一 runner 池、同一时刻,一个绿一个红 ⇒ Windows runner 池里同时存在健康机器和被污染机器(泄漏进程占测试端口 + 磁盘剩余空间耗尽,ENOSPC 出现在 job 开始后仅 2 分钟,即机器初始状态就是满的),失败只是抽签抽到坏机器。代码侧无任何嫌疑:我新增的 3 个测试在那次失败的 shard 里实际运行并通过。已触发第四次重跑。若仍抽到坏机器就只能等池子恢复;系统性解法(harness 对
fail 0 && cancelled > 0假红形态自动重试一次)的证据链已完整补进跟踪条目 #3665,等 owner 拍板。本 PR 代码本身的状态不变:定向测试全绿、typecheck 干净、其余 40 个 CI 检查全部通过/跳过,唯一红的就是这条被环境问题卡住的 windows lane;且你的实弹验证已经证明修复在真实流量下生效(压缩成功、无 400、无死锁环)。
ranxianglei commentedon Oct 18, 2026
(ai: vllm-qwen/qwen3.8-27b)
CI 全绿了。 第四次重跑(11:14–11:28 UTC)通过 —— 同一份代码、同一条 lane,换一台健康机器就过,这本身就是「前三次是环境抽签、与代码无关」的最终证明。当前 PR #3678 全部检查:30 success + 14 skipped,无红。
收工判定:PR #3678 可以合并,等人工 merge(按仓库规则 agent 不代合)。合并后如需发版请把发版决定给到我——修复内容、行为变更披露、回归审查都在 PR 描述和上面的八问回复里。
遗留两件事(都不阻塞本 issue):
fix: replay valid JSON for unparseable proxy-tool arguments in the lo…
ranxianglei commentedon Oct 18, 2026
(ai: vllm-qwen/qwen3.8-27b)
进展更新(两件事):
1. 分支已同步到最新 master(v0.1.200)。 上一轮 CI 全绿后检查发现 master 已前进到
6d9620faa(v0.1.200 发布 + #3642 wire 规范化等)。核查结论:master 对我唯一改动的文件src/loop/core.ts零改动,rebase 无冲突;#3642 的规范化工具只作用于入站方向,出站arguments仍逐字节透传(kernel/src/wire/openai.ts新代码确认),本修复在新 master 上依然成立且必要。rebase 后本地 typecheck 干净、定向测试 36/36 通过,已推送(PR head 现为d8efd7046)。2. 新一轮 CI 大部分已绿,最后一个 job 疑似挂死,已取消重跑。 新 head 的全部 e2e lane(codex/dsh/opencode/pi/fake-codex/local-registry/codex-overlay)一次通过;主 CI 里此前反复假红的
test (windows-latest, 22, 2)这次直接绿了。唯一异常:test (ubuntu-latest, 24, 1)跑了 67 分钟仍未结束(同 lane 基线 18–19 分钟,且该 workflow 无 timeout、harness 无看门狗,挂死会一直挂到 GitHub 6 小时上限)——与 #3665 记录的「测试进程挂死」是同一类基建问题,只是这次发生在 ubuntu。已取消整个 run 并触发全新重跑(新 runner),结果出来我再汇报。Merge pull request #3678 from ranxianglei/2026-10-18_malformed-proxy-…