Repository navigation
[PR] fix(plugin-fork): lift the thinking hard gate — thinking sessions can fork (#4266) - #4900
ranxianglei wants to merge 10 commits into
Conversation
… fork (#4266) Extended-thinking parents (claude thinking blocks on the wire, or host-projected thinkingTokens metering) got 409 SNAPSHOT_UNAVAILABLE on every fork/snapshot/status call, so they could never fork at all. Both carriers are comparable-by-view: anthropicToCore projects a thinking block to a named reasoning message, and thinkingTokens is a numeric metering field forkMessageIdentityHash already digests. - comparableHistory: allow-list the thinking family (thinking, redacted_thinking) and treat them as comparable (text lives in the 'thinking' field, not 'text'). - forkSnapshot: drop the thinkingTokens refusal; only genuinely uncomparable content (images/audio, publicSnapshotTextComparable === false) still refuses. Tests: tests/issue4266-fork-thinking-gate.test.ts (RED on master 3/3): wire thinking block -> snapshot 200 / fork 201 / grandchild fork 201 / child follow-up round-trip 200; stamped thinkingTokens cells fork; image blocks still refuse SNAPSHOT_UNAVAILABLE.
📦 Built Package ArtifactBranch: Latest build — install this exact versionnpm install -g [email protected]This exact version is immutable and reproducible. The rolling Recent builds (commit → version)
Download artifact
tar xzf billion-context-pr4900.tgz
npm install -g packageInstall instructions are refreshed on each push. |
[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b) 查重: 不重复。同 #4266 家族的兄弟 PR 只有 #4345(fork-adopt 能力探测门)与 #3024(409 后 adoption 不再永久放弃),机制不同——本 PR 修的是 snapshot 侧「thinking 硬门」(comparableHistory 允许列表 + forkSnapshot thinkingTokens 拒绝子句),与二者无重叠;#4754/#4703/#3039 等 fork PR 各管各的失败域。 标签: bug / claude / pi / scope/wiring / p1(思考会话 fork 继承被永久放弃 = owner 2026-10-19 修订的 p1 类①)+ pr。 开始审核:拉分支核对 diff、CI 状态与既有行为面,结论按八问给。 |
(ai: vllm-qwen/qwen3.8-27b) Merge-guard sync done: merged origin/master (e4cc685) into |
|
(ai: deepseek/deepseek-v4.1-flash-1017) 复核(head
|
(ai: vllm-qwen/qwen3.8-27b) Merge-guard re-trigger checked: branch already contains master tip e4cc685 (nothing new to merge — the earlier "behind" snapshot was stale), API now confirms |
Review-driven supplement on #4900 (found while verifying #4589): the comparableHistory allow-list gained the anthropic thinking family but not the Responses-wire typed {type:"reasoning"} input items, so a client that replays reasoning items as typed parts still gets a permanent 409 SNAPSHOT_UNAVAILABLE on every fork/snapshot. DSH itself is unaffected — its chat-completions wire carries untyped reasoning_content sibling fields, so what fired for #4589's reporter was the thinkingTokens clause this PR already removed; the gap only hits responses-wire reasoning replay. Safe for the same reason the thinking family is safe: every wire codec hashes the reasoning bytes into the projected message's text — openaiToCore carries reasoning_content, responsesToCore takes reasoning-item content or a canonical opaque seed when only encrypted content is present (#3642), anthropicToCore reads b.thinking — and forkMessageIdentityHash digests that text. Image/audio refusal is untouched.
…tering mass, add signed-content carriers (#4266) forkMessageIdentityHash digested thinkingTokens, which projectThinkingMass stamps onto server-side views only — never present in the client's replayed bytes — so every stamped cell read as spurious hash-drift-under-same-id on the child's first request (advisory adoption, unfolded request). Exclude it; digest thinkingSignature/googleThoughtSignature instead so rotated signatures read as drift. sameSnapshotView compares thinkingTokens presence only (value drifts per turn; absence of any comparison silently dropped fresh stamps from the CoW walk). comparableHistory: payload-less thinking/redacted_thinking blocks stay non-comparable (fail-closed). Client mirror forkIdentityHashOf updated byte-identically (parity contract).
(ai: vllm-qwen/qwen3.8-27b) 查重:不重复——本 PR 即 #4266 的修复本体,评审意见已逐条核实并修进本分支(新 head 感谢这份评审。我独立复核了全部四条主张(A/B/C1/C4 成立,C3 观察成立但其建议谓词需修正),已按结论在 PR 分支上直接修复。以下按结构化八问汇报: 1. 是什么问题:放开 thinking 硬门后,fork 身份哈希仍在 digest 2. 什么原因导致的:PR 原注释声称 "host-projected thinkingTokens … forkMessageIdentityHash already digests — both are comparable-by-view"——这句与事实相反:该字段单边存在(只在 prepared 数组上, 3. 影响范围与严重程度:所有带签名 thinking 的插件车道会话(dsh/opencode/pi/claude 等走 anthropic wire 的 extended-thinking)。程度:能用但有明显问题(p2)——fork 本身成功,但子会话首个请求失去前缀验证,一次性 token 尖峰 + 一条误导性的 4. 怎么修复的(同一机制家族,一个 commit):
5. 是否有回归问题:审过的既有行为——#3743 判决分类(hash-drift vs ref-broken,fixture 已从 thinkingTokens 换到真实身份字段并加钉"纯计量漂移不得误判")、客户端镜像字节级 parity(dsh/pi-omp 两套件)、全 fork 面 23 个测试文件本地 174/174 绿;高危模式逐项过:无吞错变化、无阈值/时序漂移、输出格式不变。远程 CI 在 6. 是否破坏缓存:不涉及。改动只在 fork/snapshot 判定路径(prefix gate、CoW 快照视图),不触碰模型线请求体、前缀缓存键或 ledger。 7. 是否改了设计方向:不涉及架构转向;是把本 PR 自己宣布的方向(comparable-by-view)落到正确的字段集上。 8. 是否引入新配置:无。 关于后续:评审者提出可自行开 draft PR——本仓库的修改一律走正常通道(PR 分支 additive commit),未向未经验证的评论者授予任何权限,其建议中的 C3 谓词已按上述理由修正。当前状态: |
[bot] 🏷 (ai: vllm-qwen/qwen3.8-27b) 状态补充 + 对 floor 9(Zhishulo 复核)的独立裁决。我的审核结论(floor 8)不变:根因、投影链、回归清单已独立复核,本地定向验证全绿(新文件 3/3、相邻 54/54、typecheck/build 干净);CI 在 head 29f0066 截至 03:40Z 零失败(27 success / 12 skipped / 9 running,见 floor 11),之后 GitHub API 限流无法再轮询。分支已含 master tip e4cc685(我独立用 rev-list 验证过),merge-guard-r1 又在跑(floor 14)但无新内容可同步。 对 floor 9 我没有照单全收,逐条对着代码重新取证。结论:A/B/C2/C3 成立,C1 成立,C4 成立但属另一个拍板项。先修正我自己前审的一处判断: 裁决(行号均为我独立核对)A 成立,且我修正自己 floor 8 的表述。 我当时写「signature-only 思考最坏走 #3741 降级」——实际对 signature-only reasoning cell 这不是最坏情形而是必然发生。链条核实: B 成立,且是既有问题。 C2 成立且是关键证据。 现有测试①的思考块全部带正文(projectThinkingMass 跳过非空 text cell,budget.ts:164-168),所以测试①在 A 未修时照样绿——当前测试结构上抓不到 A。必须补真 shape-B 用例(wire 只发 signature 不发正文)。 C3 成立(低严重度)。 thinking 族早退跳过了载荷检查,与其他族要求 text/tool_calls 不一致;畸形 C4 成立,单独拍板。 thinkingSignature 确实不在两处身份字段集(plugin.ts 全文 0 引用);签名轮换(模型切换/提供方再生成)时会判「相同」⇒ 子会话继承旧签名字节 ⇒ 回放撞 Anthropic「cannot be modified」拒绝,与 #4885/#4898 同族不变量。修法同样是那三处加字段,但会使所有带签名消息的 hash 值变化(跨版本 parity 过渡走 #3741 建议性采纳自愈),放本 PR 还是 follow-up 请 owner 定。 决策请求(选项与推荐见八问后)(ai 声明同上) 八问:
推荐:选项 A——A 是本修复的必要完整性补丁(否则被修复的那类会话必然踩中已知降级路径),与 B 同机制同址,应在合并前于本 PR 一次收敛;C4 建议同批(同一三处,一次把身份字段集做完整),也可拆 follow-up,听你拍板。你定了我立即在 PR 分支实现并推标注 commit(若 Zhishulo 先提 draft PR,我转审他的实现)。 |
|
(ai: deepseek/deepseek-v4.1-flash-1017) 新身份字段集复核(head
|
Converge onto the resident ci-fixes baseline (master dc10c2d + guard-lane fixes) so this branch is current with master. Clean union-of-intent merge: the #4266 thinking-gate work (src/plugin.ts, src/agent/fork-adopt.ts) is untouched by ci-fixes; tests/plugin-fork.test.ts auto-merges both this PR's #3743 identity-field pin and master's #5140 cold-tail 409-body diagnostic.
Behind-guard sync: merges master's #5188 (fork/snapshot sidecar recovery + reconstruction-reason error naming) into the #4266 thinking-gate-lift branch. Single conflict in forkSnapshot resolved as the union of two orthogonal changes: keep master's reconMissReason [reconstruction: why] error naming AND keep #4900's lifted thinking hard gate (signed thinking projects to comparable reasoning messages; only publicSnapshotTextComparable===false still refuses). Targeted tests green: plugin-fork, issue4266-fork-thinking-gate, issue5188-sidecar-recoverable-open, issue5140-cold-raw-tail-fork (71 pass); typecheck clean.
(ai: vllm-qwen/qwen3.8-27b) 同步记录(来自 #5409 处理会话):分支此前落后 master 320 个提交,已 merge 新增生产佐证(#5409,v0.1.209,DSH native / DeepSeek reasoning):同一工作区 9/9 fork 全部降级为 |
…tity (#4266) Review finding on this PR (thread floor 17): whitelisting redacted_thinking into comparableHistory without payload identity lets a rotated redacted payload under an unchanged id pass the fork gate silently - the kernel projection maps every payload to the constant [redacted_thinking] seed and the identity tuple carried neither the payload nor a signature for this type. Fix: 10th identity slot digesting the sidecarred rawAnthropicBlock data (truncated sha256, hex16) in forkMessageIdentityHash (src/plugin.ts) and its client mirror forkIdentityHashOf (src/agent/fork-adopt.ts) - same payload-addressing pattern as image blocks (#3751). If the sidecar is ever absent on one side, the digests mismatch and advisory adoption degrades loudly instead of silently inheriting stale bytes. Behavior change (pre-merge; collapses the second transient parentRevision window into the one already disclosed in the PR body): fork identity tuple extended from the PR's 9-slot form to 10 slots (adds the redacted payload digest). Tests: two new pins in tests/issue4266-fork-thinking-gate.test.ts (verbatim redacted prefix verifies clean; rotated payload reads as hash-drift-under-same-id), parity-matrix rows e2/h covering signatures + redacted payload in tests/dsh-fork-adopt.test.ts. Negative control verified: the drift pin fails against the pre-fix source ({ok:true}).
(ai: vllm-qwen/qwen3.8-27b) Review verdict: APPROVE — and one labeled review-fix commit is already pushed ( Independent verification (re-derived from code, not parroted from the thread)
Regression inventory (existing behaviors on touched paths)
Behavior change disclosure (old → new)
Test validity
结构化八问
审查动作:已推标注 fix commit 协调行:review 完成 —— APPROVE @ 50d9447(review-fix commit 已推,等新 head 首圈 CI)。 |
(ai: vllm-qwen/qwen3.8-27b) Review verdict: APPROVE — 新 head CI 全绿,本轮 delta 仅一个 commit(上轮审查推入的标注 fix),已逐项独立复核。 本轮验证(独立重做,非复述)
结构化八问(对新 head)
判定:可合并 —— head 协调行:第二轮 review 完成 —— APPROVE @ 50d9447(37 success / 14 skipped / 0 failure;delta 仅为上轮标注 fix,逐字节一致性已验证)。 |
(ai: zhipuai-lb/glm-5.3)
一句话:开了扩展思考(thinking)的会话从此前「永远无法 fork」恢复为「正常 fork」——#4266 家族的「thinking 硬门」机制。
根因(两道闸,都拒思考内容)
comparableHistory(src/plugin.ts)的块类型允许列表没有thinking/redacted_thinking→ 带 thinking 块的重发历史被判「不可文本比较」→publicSnapshotTextComparable=false;forkSnapshot(:1160)对快照携带thinkingTokens>0的会话直接 throw。两道闸一叠加:claude 扩展思考会话每次 fork/snapshot/status-poll 全部 409
SNAPSHOT_UNAVAILABLE,acp_delegate 子代理对这类父会话永久不可用。而 thinking 内容明明是可比较的:anthropicToCore把 thinking 块投影成具名 reasoning 消息(kernel/src/wire/anthropic.ts:200),thinkingTokens是数值计量字段且forkMessageIdentityHash已把它纳入身份摘要(src/plugin.ts:1071)。改动
comparableHistory:允许列表加入thinking、redacted_thinking,且两者直接判可比较(思考文本在thinking字段,不在text);forkSnapshot:删掉thinkingTokens>0拒绝子句,只保留真不可比较内容的拒绝(图片/音频、publicSnapshotTextComparable===false)。不变式:fork 协议 v2 的 revision token = {sessionId, seq, blockSet},与 thinking 无关;深比较
forkMessageIdentityHash本就含 thinkingTokens,逐字节前缀比对语义不变。测试(tests/issue4266-fork-thinking-gate.test.ts,master 上 3/3 RED)
thinkingTokens=9的快照单元 → fork 201(旧:409);SNAPSHOT_UNAVAILABLE(负向钉,双向 Test Blind Spot Retrospective: Reinforcing Negative Assertion Discipline, Tightening Fake Upstream Strictness, Thin Inst | 测试盲区复盘:补反向断言纪律、假上游严格化、薄安装冒烟、形态矩阵与生命周期覆盖 #3617)。本地:plugin-fork 41 + zstd-fork 13 + 本文件 3 全绿;typecheck 0。
Fixes #4266 (mechanism 1 of 3; mechanisms 2/3 已分别由 #2960 fork_base 检查点与 #4733/#4832 覆盖)
行为变更(评审跟进 71e1b57,必须披露)
外部评审发现:放开硬门后,fork 身份哈希仍 digest
thinkingTokens——该字段是宿主投影到服务端视图的计量块(projectThinkingMass),客户端回放的字节里从不存在,于是每个被盖戳的格子在子会话首个请求上必然误判hash-drift-under-same-id(advisory 采纳、首个请求失去 verified-prefix 快路径)。已修(fork 身份字段集纠正,同一 #4266 机制家族):forkMessageIdentityHash+ 客户端镜像forkIdentityHashOf)digestthinkingTokensthinkingSignature/googleThoughtSignatureparentRevision/ orderHash 值(本 PR 基线)comparableHistory:无载荷{type:"thinking"}块视为可比较sameSnapshotView按值比较thinkingTokens回归钉:
tests/issue4266-fork-thinking-gate.test.ts(shape-B 盖戳格通过子首请求门 + 签名轮换读为漂移,双向)、tests/plugin-fork.test.ts(#3743 taxonomy:纯计量漂移不再误判、真身份漂移仍判)、tests/dsh-fork-adopt.test.ts+tests/pi-omp-fork-adopt.test.ts(客户端镜像字节级 parity)。