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
Fold splits signed thinking from its tool_use → permanent 400 on Anthropic wire (0.1.207) #4885
Setup: pi + billion-context 0.1.207, provider github-copilot (Anthropic messages wire), claude-opus with extended thinking (interleaved, signed).
What happened:
The model called compress with a range that sat inside the current tool loop (right after the protected last-user message).
The fold removed an assistant tool_use and the following tool_use/tool_result pairs, but kept that assistant message's signed thinking block (logged as "exempt ... reasoning=24").
The orphaned thinking block was merged into the next surviving assistant message, producing [thinking A, thinking B, tool_use].
Every request since fails with:
400 messages.15.content.1: thinking or redacted_thinking blocks in the latest assistant message cannot be modified.
Retries and a host compaction don't recover it: the folded state persists, and only the index changes (15 → 5).
Expected: a fold never separates a signed thinking block from its tool_use sibling. Either fold the thinking along with its tool_use, or refuse ranges that would split them (the docs' #1658 note says signed thinking must round-trip with its tool_use).
Workaround: the host's own transcript is intact; replaying the session without the proxy works.
Same failure here on 0.1.210: pi 1.4.0 with pi-native, Claude Opus 5.5 with thinking, through an Anthropic-compatible gateway:
400 {"error":{"type":"ValidationException","message":"messages.11.content.2: `thinking` or `redacted_thinking` blocks in the latest assistant message cannot be modified. These blocks must remain as they were in the original response."}}
It started after the third compress call of the session. The earlier folds were m00001–m00012 and m00013–m00025. All 10 requests after it failed, and the last 10 of 16 prompts never ran.
Later I ran one fold session each on 0.1.211 and on the PR build 0.0.7344-pr.4898.7544462 (1 and 3 compress calls). Both finished without the error. That's one run each, so it says little about #4898, because the trigger depends on where a fold range starts.
Your passing runs on 0.1.211 and the PR build are consistent with the fix working but, as you say, one run each proves little — the trigger depends on where a fold range starts relative to a signed thinking block. That's why the PR carries a regression suite that pins the reported shape deterministically instead of relying on live-session luck.
Since your report arrived, I also re-validated today because master moved far past the PR's base:
Reproduced the orphan shape on current master code without the fix → still broken (the bug was never fixed elsewhere).
Re-synced the PR branch onto the latest master (it had fallen ~247 commits behind; one conflict with a recent kernel performance refactor touching the same integrity gate, resolved), kernel version now 0.0.177.
Re-ran the targeted suites on the synced head: new Fold splits signed thinking from its tool_use → permanent 400 on Anthropic wire (0.1.207) #4885 regression suite + reasoning-pairs + turn-integrity + recommend-integrity + tool-pair + recent-pair + wire-reasoning-echo + shared-screening (69+3 pass), bili-level thinking/replay/reconcile/orphan suites (26 pass), typecheck clean. Remote CI will do the full-suite verdict on the pushed head.
So: nothing more needed from you right now. Once PR #4898 merges and ships in the next release, your scenario should stop 400-ing. Until then the workaround stands: replay the session from the host transcript (the proxy's folded state persists across restarts, so a fresh replay is what clears it).
If the error ever recurs on a build that contains#4898, please share the session log — that would mean a second split path exists beyond the one pinned by the regression suite, and I'd want to see it.
Another data point for this issue on 0.1.213 (npm latest as of 2026-11-11), host pi 1.5.0 (pi-native plugin), provider github-copilot (Anthropic messages wire, api.business.githubcopilot.com), model claude-opus-5.5 with interleaved signed thinking. Same mechanism as #5891, reached through a slightly different path.
Observed (4 sessions, 2026-11-09 … 11-11): every thinking … cannot be modified 400 came right after a compress call whose range reached into the recent protected zone; every retry failed with the same index (messages.3.content.7, other sessions .5.content.5, .5.content.17, .1.content.42) until the model was switched (pi drops cross-model thinking blocks, so the rebuilt message then goes through). Folds that stayed outside the recent zone never triggered it.
Log signature that distinguishes the bad fold: the compress result says Excluded N protected message(s) … from compression rangewithoutRange truncated at that boundary. In pi-native.js the range is truncated at the first protected id only when firstProtectedIdx > 0; when the range starts on a protected id (firstProtectedIdx === 0) it falls through to filteredIds.filter(id => !protectedSet.has(id)), so protected items are skipped one by one and everything between them is folded. Because bash/read tool pairs are in NEVER_PRESERVE_RECENT_TOOLS, each recent turn [thinking(signed), tool_use(bash)] + tool_result loses its tool_use/tool_result but keeps the thinking → the surviving thinking blocks of several turns are adjacent assistant cores → coreToAnthropic merges them into one assistant message with 7+ thinking blocks → 400.
Session archive confirms the sieve shape (block b3, m00316–m00356): visible/FOLDED alternate exactly as reasoning-visible / bash-call-folded / bash-result-folded.
2026-11-11T22:16:49 [acp-proxy: [Compressed m00001–m00111, m00112–m00315, m00316–m00356 → 3 block(s), ~55017 tokens saved.
… Excluded 14 protected message(s) m00284, … m00315 … Range truncated at that boundary — 18 later message(s) … stay visible too (#3953).;
Excluded 16 protected message(s) m00316, m00317, m00318, m00320, m00322, m00325, m00328, m00331, m00334, m00337, m00341, m00346, m00349, m00350, m00352, m00354 from compression range (recent/last-user zone) … Last-user anchor: m00357 …]
2026-11-11T22:16:50 processTurn: 63 msgs … I1 violation … exempt carrier=0 reasoning=19 …
2026-11-11T22:16:52 ← upstream 400 … messages.3.content.7: `thinking` or `redacted_thinking` blocks in the latest assistant message cannot be modified.
Config-only workaround that removes the asymmetry (reasoning protected / its tool pair not): make the recent zone contiguous so the range is always truncated at the first protected item instead of sieved —
(CONFIGURATION.md documents [] as valid = maximum protection.) Cost: fresh bash/read results age out of the recent zone by position instead of being foldable immediately. By code reading the protected set is then a pure suffix of the visible cores (+ last-user anchor), so a reasoning core can no longer be protected while its tool_use sibling is not. I have not yet caught a live sieve-shaped fold on Opus 5.5 with this setting to confirm empirically.
Reference: Anthropic's preserved-thinking docs state the kept thinking blocks "must be an unbroken run of the original sequence" and list "Summarizes or drops old turns on the client and keeps recent turns with their thinking" as a failure mode — so for claude-*-5.x the fold has to drop a turn's signed thinking together with its tool_use (or refuse the range), never keep the thinking alone.
Activity
ranxianglei commentedon Oct 26, 2026
(ai: vllm-qwen/qwen3.8-27b)
查重: 不重复——同家族、不同机制。最近邻逐一排除:#1658(已关,#651 丢弃入站 thinking 的开关问题)、#1960(已关,in-turn 重请求改写 latest assistant)、#4679(open,bili 自身第 ≥2 轮重请求截断)、#4292/#4457(fold 孤儿化 mid-conversation SYSTEM,靠位置兜底修复)——都不是「fold 把 signed thinking 与其 tool_use 拆开」。
标签: bug, pi, scope/kernel, p1(触发后会话完全不可用,按 owner 修订属 p1 第一类)。
已复现(当前 master;你的 0.1.207 行为相同)
版本核对:
git log v0.1.207..origin/master对kernel/src/{unit-projection,turn-integrity,reasoning-pairs,tool-pairs,boundaries}.ts输出为空——折叠判定代码在 0.1.207 与 master 逐文件相同,master 上的复现即你构建的行为。最小复现:一条 assistant 消息形如
[thinking(签名)][text][tool_use],模型 compress 范围从 tool_use 起头、跨到后续 pair → 折叠成功、零报错零警告,签名 thinking 被留在外面;会话再走一轮后重建出站 wire,出现单条 assistant 消息[thinking A | text | thinking C | tool_use]——与你报的[thinking A, thinking B, tool_use]同形。provider 对 latest assistant message 做字节级签名校验 → 400;折叠态持久在账本里,重试/宿主 compaction 只会让错误索引滑动(你的 15→5)。完整可跑脚本与逐层证据见授权 issue #4886。根因(kernel 三层)
kernel/src/reasoning-pairs.ts:79-98:backward 边界调整从范围起点i-1起只回扫「连续 reasoning」消息。起点是 tool-call、而它前面隔着同一条消息的 prose(plain text 块,或映射成 text 的redacted_thinking)时,回扫第一步撞上 text 就停,thinking 拉不进范围。forward 方向是 burst-aware 的([bug] compress 保护区过滤在完整性调整之后挖洞,导致 assistant tool-call 消息缺 reasoning_content(DeepSeek 思考模式 400) #684),backward 不是——完整性门的分组(computeTurnGroups)也是 burst-aware 的,只有 backward adjustment 没跟上这个单元定义。kernel/src/turn-integrity.ts:122-136([bug] compress 保护区过滤在完整性调整之后挖洞,导致 assistant tool-call 消息缺 reasoning_content(DeepSeek 思考模式 400) #684 INV1):只拦「folded 侧有 reasoning、kept 侧有 call」的方向;你这个方向(reasoning+text kept、call+result folded)按 Compression breaks Responses assistant history ordering, causing repeated invalid_assistant_history 400s #564 注释显式放行——对 DeepSeek 类无签名线合法,对 Anthropic 签名线不成立。kernel/src/prune.ts:637-661stripOrphanedReasoning:只删「后面没有 assistant 内容跟随」的孤儿 reasoning;你这个孤儿后面还跟着自己的 prose,检测不到。且删除签名块本身就是改写历史=另一个 400,此层对 signed 块天然无效。修复方向(等 kernel 授权,批准前不动代码)
全部落在
kernel/**,按 Kernel Boundary 需你先行批准:授权申请已开 → #4886(p1,含可复现脚本、三层根因、行为变更披露、A/B/C 选项)。推荐选项 A(主修+加固一次闭环)。临时绕行
除你说的去代理重放外:选 compress 范围时别从「前面带 prose 的 tool_use」起头——从该 turn 的 thinking 块起(或更早),让整 turn 一起进范围。
八问
收工判定:机制已钉死并复现,修复方案已备好并附完整披露,卡在 kernel 授权——等你在 #4886 拍板(A/B/C)。
ranxianglei commentedon Oct 27, 2026
开工
kernel: sync unit-projection gate comment with INV3 (#4885 review pol…
kernel: pin INV3 fixpoint re-check after INV2 withdrawal (#4885 revie…
kernel: keep signed thinking with its tool_use across folds (ranxiang…
kernel: sync unit-projection gate comment with INV3 (ranxianglei#4885 …
kernel: pin INV3 fixpoint re-check after INV2 withdrawal (ranxianglei…
Merge origin/master into 2026-10-27_4885-signed-thinking-fold-split (…
kernel: keep signed thinking with its tool_use across folds (ranxiang…
kernel: sync unit-projection gate comment with INV3 (ranxianglei#4885 …
kernel: pin INV3 fixpoint re-check after INV2 withdrawal (ranxianglei…
Merge upstream/master (e209109) into fork master
kernel: fold signed thinking with its run (batched read loops)
worldofgeese commentedon Nov 1, 2026
Same failure here on 0.1.210: pi 1.4.0 with pi-native, Claude Opus 5.5 with thinking, through an Anthropic-compatible gateway:
It started after the third
compresscall of the session. The earlier folds were m00001–m00012 and m00013–m00025. All 10 requests after it failed, and the last 10 of 16 prompts never ran.Later I ran one fold session each on 0.1.211 and on the PR build 0.0.7344-pr.4898.7544462 (1 and 3
compresscalls). Both finished without the error. That's one run each, so it says little about #4898, because the trigger depends on where a fold range starts.Merge origin/master into 2026-10-27_4885-signed-thinking-fold-split (…
ranxianglei commentedon Nov 1, 2026
(ai: vllm-qwen/qwen3.8-27b)
Thanks for the data point — your 0.1.210 failure is expected, not a miss of the fix:
compresscall corroborates the bug exactly as reported.Since your report arrived, I also re-validated today because master moved far past the PR's base:
So: nothing more needed from you right now. Once PR #4898 merges and ships in the next release, your scenario should stop 400-ing. Until then the workaround stands: replay the session from the host transcript (the proxy's folded state persists across restarts, so a fresh replay is what clears it).
If the error ever recurs on a build that contains #4898, please share the session log — that would mean a second split path exists beyond the one pinned by the regression suite, and I'd want to see it.
kernel: add regression test for #5891 recent-zone signed-orphan shape…
chore: sync #4898 with master 82e2d53 (kernel version conflict only)
Yang-Code01 commentedon Nov 11, 2026
Another data point for this issue on 0.1.213 (npm latest as of 2026-11-11), host pi 1.5.0 (pi-native plugin), provider
github-copilot(Anthropic messages wire,api.business.githubcopilot.com), modelclaude-opus-5.5with interleaved signed thinking. Same mechanism as #5891, reached through a slightly different path.Observed (4 sessions, 2026-11-09 … 11-11): every
thinking … cannot be modified400 came right after acompresscall whose range reached into the recent protected zone; every retry failed with the same index (messages.3.content.7, other sessions.5.content.5,.5.content.17,.1.content.42) until the model was switched (pi drops cross-model thinking blocks, so the rebuilt message then goes through). Folds that stayed outside the recent zone never triggered it.Log signature that distinguishes the bad fold: the compress result says
Excluded N protected message(s) … from compression rangewithoutRange truncated at that boundary. Inpi-native.jsthe range is truncated at the first protected id only whenfirstProtectedIdx > 0; when the range starts on a protected id (firstProtectedIdx === 0) it falls through tofilteredIds.filter(id => !protectedSet.has(id)), so protected items are skipped one by one and everything between them is folded. Becausebash/readtool pairs are inNEVER_PRESERVE_RECENT_TOOLS, each recent turn[thinking(signed), tool_use(bash)] + tool_resultloses its tool_use/tool_result but keeps the thinking → the surviving thinking blocks of several turns are adjacent assistant cores →coreToAnthropicmerges them into one assistant message with 7+ thinking blocks → 400.Session archive confirms the sieve shape (block
b3,m00316–m00356): visible/FOLDED alternate exactly as reasoning-visible / bash-call-folded / bash-result-folded.Config-only workaround that removes the asymmetry (reasoning protected / its tool pair not): make the recent zone contiguous so the range is always truncated at the first protected item instead of sieved —
{ "compress": { "neverPreserveRecentTools": [] } }(
CONFIGURATION.mddocuments[]as valid = maximum protection.) Cost: freshbash/readresults age out of the recent zone by position instead of being foldable immediately. By code reading the protected set is then a pure suffix of the visible cores (+ last-user anchor), so a reasoning core can no longer be protected while its tool_use sibling is not. I have not yet caught a live sieve-shaped fold on Opus 5.5 with this setting to confirm empirically.Reference: Anthropic's preserved-thinking docs state the kept thinking blocks "must be an unbroken run of the original sequence" and list "Summarizes or drops old turns on the client and keeps recent turns with their thinking" as a failure mode — so for
claude-*-5.xthe fold has to drop a turn's signed thinking together with its tool_use (or refuse the range), never keep the thinking alone.chore: sync #4898 with master 33104b1 (kernel version conflict only)