Skip to content

[PR] fix: validate global cacheKeepalive block at config load (#4200) - #4233

Open
ranxianglei wants to merge 6 commits into
masterfrom
2026-10-22_validate-global-cachekeepalive
Open

ranxianglei wants to merge 6 commits into
masterfrom
2026-10-22_validate-global-cachekeepalive

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

What & why

The GLOBAL cacheKeepalive block at the config root was never validated on file load: it was only listed in KNOWN_TOP_LEVEL_KEYS, so a hand-edited ~/.config/billion-context/billion-context.json containing e.g. {"cacheKeepalive": {"ttlFactor": "high"}} loaded clean while the identical typo under providers.<url>.cacheKeepalive rejected loudly — same typo behaving differently by nesting level (config-surface silent-failure class). The consumer (resolveCacheKeepalive) then silently dropped the bad field to its default.

Fix (commit 1)

loadConfigFile now runs the same strict #1909-stance parser (parseCacheKeepaliveSettings) on the global block — validation only, well-formed blocks pass through as-is, mirroring parseRouteEntry. An invalid block throws into the file-load catch: one loud error naming the exact field, and the file is treated as unreadable ({}) — exactly how a malformed JSON file is handled today. null stays "absent" per the existing web-save contract (src/web/api.ts skips null for this block).

Follow-up (commit 2, small fix found while working)

The web whole-file save path called that same throwing parser without a try/catch: saving a bad global block via the web UI produced an unhandled rejection (error log, client's PUT gets no response) instead of the clean 400 every other field gets. Now caught → 400 with the parser's message. The file was never written before either, so no data impact.

Behavior change (intended, disclosed per policy)

  • Old: invalid global cacheKeepalive → silently ignored (per-field fallback to defaults), rest of the config kept working; web save of one → request dies without a response.
  • New: invalid global cacheKeepalive → whole config file rejected at load (one error log naming the field; all keys fall back to defaults/env until fixed); web save of one → clean 400 naming the field.
    Valid files are byte-for-byte unaffected. The web UI already refused these values on save, so only hand-edited files hit the load-side change.

Tests

New tests/issue4200-global-cachekeepalive-validation.test.ts (7 tests): issue repro (bad ttlFactor → {} + field-named error, logged once across repeated reads, cleared by fixing the file), unknown-key rejection, route-level symmetry pin, valid block pass-through reaching the resolver, null=absent contract, web save 400-with-field-name + file untouched, web save of a valid block still succeeds. Targeted suites green locally on the rebased head (this file + cache-keepalive + issue3572-jsonc-config + issue3572-web-jsonc-config + config-file-cache: 57/57); typecheck + build clean; full-suite verdict from CI.

Closes #4200

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-22_validate-global-cachekeepalive (e93f579)

Latest build — install this exact version

npm install -g billion-context@0.0.6191-pr.4233.f2ee720

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Recent builds (commit → version)

commit version build
e93f579 billion-context@0.0.6191-pr.4233.f2ee720 build
98f0533 billion-context@0.0.6102-pr.4233.606704e build
25fe630 billion-context@0.0.6010-pr.4233.5569899 build

Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr4233.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei ranxianglei changed the title fix: validate global cacheKeepalive block at config load (#4200) [PR] fix: validate global cacheKeepalive block at config load (#4200) Oct 21, 2026
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

查重:不重复。 检索 open+closed issue 与近期 PR,无其他条目认领「全局 cacheKeepalive 块的文件加载校验」;本 PR 是 #4200 的唯一工作项(#4200 线程内此前的分析楼层正是产出本 PR 的接单工作,不是并行认领)。

标签:bug / p3 / scope/wiring(+pr) —— 配置面静默失效缺陷,仅手工编辑配置文件可触发(web 保存路径本来就拒这些值),失效后果是整块回落默认值加一条错误日志、无数据影响,判 p3;改动面在请求管道的配置加载与 web 保存路径,scope/wiring。已打。

审核结论(独立复核,未引用 #4200 线程已有结论)

判定:rebase 后可合。 代码审查通过;CI 两条红腿判定与本机红风暴同源、非本 PR 引入(证据见第 5 问),建议 rerun-failed-jobs 转绿后合并。分支落后 master 120 个提交,GitHub 报 mergeable=true 无冲突,合并前需先 rebase。

八问:

  1. 是什么问题:~/.config/billion-context/billion-context.json 里全局 cacheKeepalive 块的拼写错误(如 "ttlFactor": "high")在文件加载时静默通过——同样的错写在 providers.<url>.cacheKeepalive 下却被大声拒绝;随后消费端把坏字段悄悄丢回默认值。同一个 typo,嵌套层级不同行为不同,典型的配置面静默失效。
  2. 什么原因导致的:loadConfigFile 对顶层键只做 KNOWN_TOP_LEVEL_KEYS 键名白名单检查,从不校验块内容;严格解析器([feature] configurable wire-path table (per-provider custom paths) - Claude Desktop's claude.ai private protocol as motivating case #1909 立场,feat: proxy-side idle prompt-cache keepalive (TTL refresh pings) | feat: 代理层空闲期提示缓存保活(prompt cache TTL 续命) #3376 落地)只接在了路由级入口上,全局块当年加入时没补这一半。
  3. 影响范围与严重程度:仅手工编辑配置文件的路径受影响(web 整文件保存还有一个连带洞:保存坏的全局块会触发未捕获异常、客户端 PUT 拿不到响应——本 PR 顺手修掉)。后果是「该块被整体忽略 + 一条点名错误日志」,无数据丢失,keepalive 默认关闭 → p3。
  4. 怎么修复的:文件加载路径在与路由级相同的位置对全局块跑既有严格解析器 parseCacheKeepaliveSettings——纯校验,合法块逐字节原样放行(镜像 parseRouteEntry 的做法);非法则抛进文件加载既有的 catch → 一条点名具体字段的错误日志 + 文件按不可读处理({}),与今天处理坏 JSON 文件的方式完全一致。null 继续表示「不存在」(web 保存契约不变)。follow-up 小修:web 整文件保存路径上同一个会抛的调用原来没有 try/catch,现在捕获 → 干净的 400 并点名字段(原先是 unhandled rejection、客户端挂起);文件本来就没写进去过,零数据影响。
  5. 是否有回归问题:逐项审过既有行为——(a) 合法文件:字节级不受影响,resolver 输入不变;(b) 坏 JSON 文件:处理路径不变;(c) 路由级块:不变,测试钉了同构性;(d) 同函数相邻字段:parseCompressSettings 等不抛异常(函数体零 throw),无误伤面;(e) 唯一语义变化 = 非法全局块从「静默忽略」变「整文件拒绝」,有意变更,披露见下。CI:本 PR head 全部静态门禁 + e2e 车道绿;ubuntu-22 三分片(全量文件覆盖)全绿、windows-22 分片 2 绿——若存在由本 diff 引入的确定性测试失败,必然在这四个腿之一暴露;红的只有 ubuntu-24 单腿全量套件与 windows-22 分片 1。交叉证据:近 8 小时 master 自身(run 39938590908 @ fdbddd0)与约 8 个其他分支持续红——仓库级红风暴进行中,且两个已知根因的修复 PR([PR] fix: hydrate pooled session envelopes from disk when the unified sidecar fails to open (#4041) #4067 修 CI intermittently red: Two storage hydration tests fail to read session records just flushed to disk under parallel load | CI 偶发红:两个存储水合测试在并行负载下读不到刚落盘的会话记录(#3769 家族) #4041 存储水合偶发、[PR] fix: reserve output headroom by route, not wire — caliber split (redo of reverted #4184) #4236 重做 "Output reservation extended to all wires" (#4184) landed without test coverage: widespread red on master is blocking th | 「输出预留扩到所有 wire」(#4184) 落地没带测试面: master 大面积红卡住 v0.1.204 发版 (已 revert, 重做要求见内) #4205 窗口口径回归)均尚未合并,这些失败机制此刻仍活在 master 上。本 PR 改动是纯 JS 对象校验、无平台/node 依赖语义,grep 确认没有任何其他测试套件写过全局 cacheKeepalive 夹具,两条红腿无法归因于本 diff。本环境取不到 job 日志、无法点名具体失败测试——诚实边界在此;建议 rerun-failed-jobs:若重跑后 ubuntu-22 保持绿而这两腿仍红,按 §7.2 判为平台/基建 flake。
  6. 是否破坏缓存:否。config-file-cache 语义不变(坏文件按 {} 缓存到字节变化为止,与坏 JSON 今天同机制);前缀缓存接缝、重试、去重键、增量游标均未触碰。
  7. 是否改了设计方向:否。只是补齐既有 [feature] configurable wire-path table (per-provider custom paths) - Claude Desktop's claude.ai private protocol as motivating case #1909 严格立场缺失的一半(路由级校验、全局级不校验),无新架构、数据流不变。
  8. 是否引入了新的配置:否。无新 key/env/flag。

行为变更披露(有意):旧 = 非法全局 cacheKeepalive 被静默忽略(逐字段回落默认)、其余配置正常工作;web 保存一个 → 请求死掉无响应。新 = 非法全局 cacheKeepalive → 整个配置文件在加载时被拒绝(一条点名字段的错误日志,所有键回落默认/env 直到修好);web 保存一个 → 干净 400 点名字段。合法文件字节级不受影响;因 web UI 本就拒绝这些值,加载侧变化只会被手工编辑的文件命中。

审核中发现的小问题(留痕):parseCacheKeepaliveSettings 对空 {} 返回 undefined,导致 web 保存 {} 得 400 而文件加载接受 {}——既有不对称,本 PR 未触碰,判定不构成缺陷(保存空块本身无意义,拒绝合理),不另立项。

合并判定:代码审查通过,rebase 后可合;CI 两条红腿判定为进行中的仓库级红风暴(master 同样红、已知机制修复均未合入),rerun-failed-jobs 或 rebase 重新触发转绿后即可合并。合并动作留给你。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

(ai: vllm-qwen/qwen3.8-27b)

查重: 不重复。 检索 open+closed issues/PRs(cacheKeepalive 标题精确 + validation 关键词组合):仅有源 issue #4200 与本 PR,无其他在途条目覆盖「全局块加载期校验」或「web 保存 400」这两个面。

标签: bug scope/wiring p3(配置面静默失效类缺陷;配置加载/请求管道面;仅手改文件受影响,严重度与源 issue 一致)

独立审核(未复用 #4200 的结论;重读了 diff、loadConfigFile 全路径与 web 保存路径):

  1. 是什么问题:配置文件根部的全局 cacheKeepalive 块只登记在 KNOWN_TOP_LEVEL_KEYS,加载时从不做严格解析——ttlFactor: "high" 这类笔误在根部能干净加载,同样的笔误挂在 providers.<url> 下却响亮拒绝;同一笔误按嵌套层级行为不同(J 类配置面静默失效),消费端还会把坏字段悄悄落回默认值。
  2. 什么原因导致的:loadConfigFile 的加载路径对全局块没有校验调用(route/per-model 两级都有)。修复打到了根因:把同一个严格解析器补进文件加载路径(只校验、合法块原样透传),不是症状层拦截。已核实插入点 src/config.ts:2268-2269 位于 try 块内,throw 落入既有 catch → 一条 [acp-config] failed to parse <file>: ...cacheKeepalive.ttlFactor must be a number in (0, 1] 错误日志,损坏内容缓存为 {}——与坏 JSON 文件的既有处理完全同构(每次内容变更只记一次日志,修好即停)。
  3. 影响范围与严重程度:只影响手改 billion-context.json 且全局块写错类型/未知键的用户(web 保存侧此前对这类值是请求无响应而非干净拒绝,commit 2 一并修掉)。轻微(p3):修复前是字段静默降级、服务可用;修复后整个文件被拒直到改正。
  4. 怎么修复的:两个 commit。① loadConfigFile 增加 parseCacheKeepaliveSettings([feature] configurable wire-path table (per-provider custom paths) - Claude Desktop's claude.ai private protocol as motivating case #1909 严格立场),保留 null=absent(web 保存契约);② web 整文件保存路径原来裸调这个会 throw 的解析器,坏值触发 unhandled rejection(客户端 PUT 拿不到任何响应),现 try/catch → 400 带字段名。已核实写盘顺序:atomicWriteConfig 在所有校验之后,修复前文件也从未被写入,无数据影响;新 try/catch 形态与相邻 parseRouteEntry/validateHttpProxy 站点逐字同构。
  5. 是否有回归问题:逐项过 diff 触碰路径的既有行为——(a) 合法配置文件:字节级不变(解析结果未被使用、无副作用),本地定向套件全绿(见下);(b) 非法全局块:有意变更且已在 PR 正文披露(旧:部分加载+逐字段静默回退 → 新:整文件拒绝+单条点名错误),波及半径与今天坏 JSON 文件的处理相同(含热重载时路由清空这一后果),先例一致;(c) 空对象 {} 全局块:照常加载(解析器返回 undefined 不 throw);web 保存 {} → 400 是既有行为(旧代码同样有 ===undefined 检查),本 PR 未改变;(d) 缓存/重试/去重:configFileCache 仍以原文为键,log-once 语义不变(测试钉住);(e) wire 格式/kernel/配置 schema/env 变量:全部未触碰。未发现无意回归。
  6. 是否破坏缓存:不涉及(无缓存/重试/游标/去重键策略变化;配置文件缓存语义原样)。
  7. 是否改了设计方向:没有。沿用既有严格校验立场([feature] configurable wire-path table (per-provider custom paths) - Claude Desktop's claude.ai private protocol as motivating case #1909),把全局块补齐到与 route/per-model 同级,方向与 issue 建议修法一致。
  8. 是否引入新配置:没有(无新 env/config 字段,纯校验收紧)。

测试有效性:7 个测试断言都有牙(deepEqual 到 {}、正则钉住精确字段名错误文本、log-once 计数器、PUT 状态码+落盘内容核对),双向覆盖齐(非法拒/合法通/null 缺省/route 级对称钉住),复现了 issue 原始场景。我在 PR head 本地复跑:PR 声称的 57/57(issue4200 + cache-keepalive + issue3572-jsonc ×2 + config-file-cache)✅,另加相邻的 config-surface-guard/config-epoch/knobs-config-precedence 26/26 ✅。

CI 现状(合并关键):42 个 check run 中 40 绿/skip,两个红 lane 已查日志归因:

  • test (ubuntu-latest, 24, 1):5 处失败全部与 diff 无关——tests/plugin-terminal-usage.test.ts 3 处(2×10s 超时 + 紧随超时后的 1 处 hash 断言级联;该文件在本 head 本地 9/9 绿,判为分片并发负载 flake,本 PR 未触碰 terminal-usage 路径);另 2 个 verif SMT 测试违反「无求解器自动 skip 不会红」的设计约定(AGENTS.md KDD release v0.1.4 #14):TAP 行带 # SKIP 注解但 worker exitCode=1,错误为 ModuleNotFoundError: No module named 'cvc5'——runner python 没装 cvc5 时探测子进程失败未被收敛。已单独立项: CI test lane: verif SMT tests go red instead of auto-skipping when the runner's python lacks cvc5 #4431(含完整 TAP 签名与建议修法)。
  • test (windows-latest, 22, 1):作业日志下载截断(可见段零 not ok、无 TAP 汇总),签名无法确认,建议 rerun。
    其余相关 lane(static-gates ×2、mysql-contract、gate、plan、lane-guard、其他 7 个 test 分片)全绿。

判定:可合。 方向正确、diff 最小(3 文件 +178/-1,每行都与目的相关)、遵循仓库惯例、测试有牙、行为变更已按政策披露(旧→新+原因)。建议 rerun 上述两个红 job(均与本 diff 无关),转绿后合并即可。本次纯审核,我未推送任何 commit——diff 本身没有需要顺手修的缺陷。

ci-check added 4 commits October 26, 2026 10:19
master window commits 293ae65/de5e03659 bumped kernel/package.json to
0.0.165 without regenerating the lockfiles; lock-sync re-resolution then
rewrites the stale recorded versions (root 0.0.163, kernel 0.0.164) and
fails. Pure recorded-version sync via npm install --package-lock-only;
no dependency or semantic change.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working p3 scope/wiring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

config: global cacheKeepalive block is never validated — invalid entries are silently ignored

1 participant