Repository navigation
fix(loop+wire): preserve redacted_thinking blocks on the Anthropic wire (#1960) - #1980
Merged
Merged
Conversation
added 2 commits
October 3, 2026 16:22
… re-request (#1960) Anthropic rejects a modified latest assistant message with 400 "thinking or redacted_thinking blocks ... cannot be modified". The in-turn compress re-request rebuilt that message from loop records, but a redacted_thinking block arrives whole in content_block_start (no delta carries its payload), so nothing was recorded and the rebuild silently dropped it — every request in the session then 400'd. - adapter-anthropic: capture the opaque redacted_thinking payload at content_block_start and emit a dedicated redacted_thinking event at block stop instead of sealing an empty reasoning segment - core: accumulate one round's assistant parts (reasoning / text / redacted) in STREAM order and emit them position-exact on re-request; redacted parts ride the rawAnthropicBlock sidecar, which coreToAnthropic re-emits verbatim - recovery: a 400 matching "cannot be modified" skips the degraded strip retry (it can only produce a second guaranteed 400) and surfaces the original upstream error Tests: tests/issue1960-loop-redacted.test.ts (byte-exact redacted replay, plain signed-thinking regression guard, no-retry recovery). Preflight: typecheck clean, targeted loop/thinking suites 185/185 green, build OK.
…eady path (#1960) The steady request path (client echoes the round back) silently dropped redacted_thinking blocks because anthropicToCore had no case for them, so every later request rebuilt the latest assistant message without the block and Anthropic answered 400 "cannot be modified" forever — the half of #1960 that kept the session stuck after the compress turn. - acp-kernel pin 0.0.99 → 0.0.101 (exact), which carries kernel PR #500 (redacted_thinking sidecar round-trip). Also rides along from v0.0.100+: #483 ref capacity widened to 9,999,999, #484 case-insensitive protected tool matching, #478 nudge cadence re-anchor, #493 language-preservation behind an opt-in flag (default off), and #499 prune/recommend shared per-turn index (hundreds of ms saved on ordinary requests in large sessions — release-note material). - tests/issue1960-steady-redacted.test.ts: client-echoed [thinking(sig), redacted(data), tool_use] must survive prepare→outbound byte-identical in order. Verified failing on 0.0.99, passing on 0.0.101. Preflight: typecheck clean; targeted loop/thinking/reasoning suites 186/186 green incl. both new #1960 files; build OK.
ranxianglei
force-pushed
the
2026-10-03_1960-redacted-thinking
branch
from
October 3, 2026 08:24
8db3169 to
29f60d3
Compare
Contributor
📦 Built Package ArtifactBranch: Option A — Install this exact build (recommended)npm install -g billion-context@0.0.2001-pr.1980.e5d91d9This exact version is immutable and reproducible. The rolling Option B — Download artifact
tar xzf billion-context-pr1980.tgz
npm install -g packageInstall instructions are refreshed on each push. |
Owner
Author
Owner
Author
[bot] 🏷 Review 完成 — 结论:可合并(人工 merge;pin bump 属跨仓依赖,按 §7.4 必须留人) 独立复核(未重做):拉分支 验证证据
结构化七问
两点备注(不阻塞)
|
… filters (#1960 filter variant) Anthropic thinking blocks and Gemini thoughtSignature parts are verified byte-for-byte when the latest assistant message is replayed, and the bytes the proxy forwards become the client's persisted history. The #1882 prose filters on the thinking/thought channels desynchronized text from signature on BOTH sides: the loop's re-request rebuild carried cleaned text, and the client echoed the cleaned bytes forever — the session 400s on every request after the first filtered thinking delta. Remove the thinking/thought filter instances; the TEXT channel keeps the #206/#1881 strips. Cosmetic tag/marker echoes inside a thinking pane are accepted (#1960 verdict). - adapter-anthropic: thinking_delta forwards + accumulates verbatim (raw chunk passthrough, no flush tail); redacted capture from #1980 unchanged - adapter-google: thought parts forward verbatim with their thoughtSignature; no editedParts rewrite for thought - tests: issue1960-thinking-verbatim (loop-level byte equality on re-request AND client-visible stream, both wires); issue1881 pins flipped for the two SIGNED channels (openai reasoning_content / responses summaries keep the strip) - AGENTS.md: Key Design Decision #10 — thinking-family payloads are signature-verified, never rewrite them; future changes need loop-level byte-equality tests on both signed wires
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(loop+wire): preserve redacted_thinking blocks on the Anthropic wire (#1960)
Fixes #1960
What was wrong
With Claude extended thinking, an in-turn compress left the session permanently
broken: the compress re-request 400'd with
and every later request in the session returned the same 400.
Root cause (verified by controlled repro driving the real
runCompressLoop):a
redacted_thinkingblock arrives whole incontent_block_start— nodelta ever carries its opaque payload — and nothing in either pipeline recorded
it:
message from recorded parts (
[thinking, tool_use]) without the redactedblock that sat between them → Anthropic rejected the modified message.
anthropicToCorehad no case forredacted_thinking, so every client request echoing the round back silentlydropped the block → the same modified shape went out forever. This is why
the session could never recover.
The reporter's step-1 hypothesis (
hideConsumedCompressCallsdropping thecurrent call) was checked and disproven: the executed compress call is kept
(its id is stamped onto the new active block, plus the newest-orphaned tail
scan), and the incident log has no
hideConsumed hidline.Changes
Loop (src/loop/core.ts, src/loop/adapter-anthropic.ts)
content_block_startand emits a dedicated
redacted_thinkingevent at block stop (instead ofsealing an empty reasoning segment).
stream order and emitted position-exact on the re-request; redacted
parts ride the
rawAnthropicBlocksidecar, whichcoreToAnthropicre-emitsverbatim (same mechanism images already use).
cannot be modifiedskips the degradedstrip-retry — that retry can only produce a second guaranteed 400 plus a
swapped error — and surfaces the original upstream error immediately.
Steady path (acp-kernel pin 0.0.99 → 0.0.101)
redacted_thinkingcase toanthropicToCore(sidecar-carried, verbatim on the way out).large sessions save hundreds of ms;
All fixes or default-off — no default-behavior drift expected beyond what
is listed above.
Tests
tests/issue1960-loop-redacted.test.ts(new, 3 cases): byte-exact +position-exact redacted replay in the re-request; plain signed-thinking tail
unchanged by the ordered-parts restructure; no-retry recovery on the
cannot be modified400 class.tests/issue1960-steady-redacted.test.ts(new): a client-echoed[thinking(sig), redacted(data), tool_use]survives prepare→outboundverbatim. Fails on acp-kernel 0.0.99 (verified), passes on 0.0.101.
186/186 green incl. both new [acp-loop] In-turn compress re-request edits the latest assistant message → Anthropic 400 "thinking blocks … cannot be modified" (session stays broken) #1960 files; build OK. Full suite via CI.
Behavior changes (flagged per policy)
redacted_thinkingblockson every request (any session containing one was doomed to repeated 400s);
now they are preserved byte-exact while their messages stay unmodified or
are pruned with range. Old → new: dropped → preserved verbatim.
redacted blocks in stream position (previously lost — the defect). Plain
signed-thinking shapes are byte-identical to before (regression-tested).
cannot be modified(it guaranteed a second failure); the client sees theoriginal upstream error text. Other 4xx classes retry exactly as before.
Config surface
None — no config field added, renamed, or re-semantics'd. No env vars, no CLI
flags.