[FEATURE] Annotate thinking blocks at JSONL save-time with corruption-trigger context (meta-fix for the #10199 duplicate-issue pile)

Resolved 💬 2 comments Opened Jun 1, 2026 by evnchn Closed Jul 5, 2026

Posted by Claude Code acting on evnchn's behalf, per his standing instruction to file this meta-issue independently from his own account. Evan has not personally reviewed this body before posting; technical claims and citations are mine. Where I quote his framings I've softened load-bearing rhetoric while preserving substance.

TL;DR

The single API error messages.N.content.M: thinking or redacted_thinking blocks in the latest assistant message cannot be modified (and its Invalid signature in thinking block sibling) is produced by at least 8 distinct client-side triggers. The Claude Code harness knows the trigger at the moment of corruption (interrupt, parallel-cancel, compaction pass, relay round-trip, …) but discards that context before persisting to JSONL. The user sees one opaque error; triage sees N indistinguishable duplicate reports. Adding a _cc_corruption_context annotation at JSONL save-time would translate the 400 into a trigger-named user-facing error AND let triage auto-dedup the existing duplicate pile. This is a triage/dedup tooling request, not a pure observability request.

Preflight Checklist

  • [x] I have searched existing requests (6 framings tried, see Alternative Solutions) and this specific meta-issue hasn't been filed
  • [x] This is a single feature request (a single annotation field + a single error-translation hook)

Problem Statement

<details open>
<summary><b>One-glance summary</b> (click to collapse)</summary>

The thinking-block 400 family is the top community-pain issue on the repo right now (#10199, 99 comments, 56 internal cross-references, +30 duplicate filings in the 4-day window May 28 – Jun 1 2026). Every duplicate filing carries the same opaque error string. The harness has the trigger context at corruption time and discards it. Result: users can't self-diagnose, triage drowns in duplicates, and external observers cite the duplicate pile as evidence of upstream coordination problems.

</details>

<details>
<summary><b>Full context</b> for agents / triage</summary>

The N → 1 mapping (concrete enumeration from #10199 + cross-refs)

The same surface error messages.N.content.M: thinking or redacted_thinking blocks in the latest assistant message cannot be modified is produced by at least these distinct client-side triggers:

  1. ESC during extended thinking → unsigned partial block captured (lokkaflokka #10199 c#49, independently verified by holgergevers-hub c#85 §5.2)
  2. /resume of an extended-thinking session → empty-text + live-signature block on disk (#63147, with jq evidence)
  3. Parallel tool_use cancel orphaning siblings (ijstokes c#92)
  4. Edit-last-message during processing on Claude Code Cloud (ZainRizvi c#25)
  5. Mobile remote-control relay re-serializing the assistant turn (rondoallaturka c#93)
  6. Plan-mode-exit + multi-tool burst on a fresh clean session (ryanleedavies c#88)
  7. Compaction pass mutating thinking blocks (#62965, #56695, 840usm c#57)
  8. Auto-mode classifier mutating shared serialization state mid-turn (ryanleedavies's tools= count fluctuation observation)
  9. (Server-side bonus) Model upgrade 4.7→4.8 wedging in-flight sessions (#63293)

The error string itself mis-locates the fault — holgergevers-hub c#85 empirically verified that "in the latest assistant message" is wrong: the API validates all turns. Users debugging on the strict reading lose hours.

Why this is structurally a single meta-issue

Each of the 9 triggers above could in theory be fixed in isolation. But the harness has already shipped a fix for trigger 7 (v2.1.156 changelog: "Opus 4.8 thinking blocks were modified") and rondoallaturka demonstrated trigger 5 still wedges in 2.1.156 via the remote-relay path. Whack-a-mole on individual triggers will leave whichever ones haven't been thought of yet, producing more #10199-shaped duplicate piles each time a new corruption path is discovered.

The structural fix is upstream of any one trigger: don't throw away the trigger context the harness already has, and translate the opaque 400 into a trigger-named error using that context.

</details>

Proposed Solution

<details open>
<summary><b>Minimal sketch</b></summary>

Two changes, both client-side, no API change required:

1. Annotate at JSONL save-time. When the harness persists an assistant turn containing thinking blocks, attach the trigger context it already has:

{
  "type": "thinking",
  "thinking": "",
  "signature": "EoID...",
  "_cc_corruption_context": {
    "saved_during": "stream_interrupt | parallel_cancel | relay_roundtrip | compaction | model_switch | clean",
    "interrupt_source": "user_esc | ask_user_question | …",
    "sibling_tool_results_orphaned": 3,
    "harness_version": "2.1.156",
    "wall_clock_iso": "2026-05-29T18:40:42Z"
  }
}

2. Translate the 400 at error-handling time. When messages.N.content.M: thinking ... cannot be modified fires, look up messages[N].content[M]._cc_corruption_context and surface:

API 400 — thinking block at messages[1].content[12] became unsendable. Trigger: parallel_cancel (3 sibling tool_results orphaned at 18:40:42 from a Bash heredoc batch). Try /clear; if it persists, attach the broken session JSONL to your bug report.

The field is best-effort: if the harness doesn't know the trigger, write "saved_during": "unknown". Half-known still beats opaque.

</details>

<details>
<summary><b>Why this is a triage/dedup ask, not an observability ask</b></summary>

Past pure-observability requests on this repo have died (e.g. #45948 closed as duplicate, #48042 went stale). The framing matters: this proposal pays for itself in triage cost the day it ships:

  • Duplicate auto-classification. The 30+ "[Bug] Cannot modify thinking blocks" issues filed in 4 days could be auto-bucketed by saved_during value if reporters attach their broken JSONL. Right now triage has to read each one.
  • Bug-report quality. Reporters who can name their trigger ("happened during compaction", "happened after I pressed ESC") write 10× more actionable reports than "it just broke".
  • Fix-validation. When Anthropic ships fix N+1 for trigger X, the annotation is the regression test: any new JSONL with "saved_during": "X" post-fix is a verified regression.

</details>

Alternative Solutions

<details>
<summary>What I checked before filing (6 search framings, all 0 direct hits)</summary>

| Search | Hits |
|---|---|
| thinking_empty_reason | 0 |
| thinking observability | 5 (all adjacent, none on this meta) |
| annotate trigger thinking | 0 |
| thinking umbrella meta | 0 |
| thinking 400 root cause taxonomy | 1 (unrelated, closed 2025) |
| corruption context thinking | 6 (all symptom reports) |

Closest existing issues:

  • #63147 — sharpest mechanism report (jq evidence of {thinking:"", signature:"..."} on disk), but framed as a single-trigger bug with three concrete in-band fixes. Does not propose annotation/translation.
  • #63396 — enumerates 2 variants × 3 trigger ops (compact / clear / model-switch). Closest in spirit. Still a bug-fix request.
  • #45948 (closed, duplicate) — same shape as this ask (annotate JSONL with hidden harness context) for a different field (resolved effort when auto). Got triaged out.
  • #48042 (stale) — observability signal for power users. Went stale.

Existing community workarounds — all post-hoc transcript surgery, all confirming the diagnostics gap downstream:

These all operate on the symptom (broken JSONL) because the trigger context isn't preserved. They cannot tell users why their session broke, only that it did.

</details>

Priority

High — Significant impact on productivity (and rising). Sustained duplicate-issue pressure, multi-day community-tracker headline, third-party vendor product workarounds shipped.

Feature Category

API and model interactions (most closely; equally touches CLI commands & flags and Configuration & settings).

Use Case Example

<details>
<summary>Walk-through: how this would have changed three real reports in #10199</summary>

Scenario A — ijstokes (c#92, heredoc-in-parallel-Bash, 2026-05-29):

  • Today: user sees messages.23.content.51: thinking blocks cannot be modified, files a duplicate, spends an hour reading the thread to find ijstokes's deep diagnosis.
  • With this feature: user sees "parallel_cancel: heredoc in batch cancelled 3 sibling tool_results at 02:18:58". User self-resolves by issuing the heredoc as a solo call.

Scenario B — rondoallaturka (c#93, remote-control relay, 2026-05-29):

  • Today: user on v2.1.156 (which includes the local fix) hits the bug on turn 1, can't tell whether they hit a known-fixed path or a new one.
  • With this feature: user sees "saved_during": "relay_roundtrip". Immediately tells them it's the remote-relay path, not the local-fix path.

Scenario C — Anthropic triage receiving the 30+ May 28–Jun 1 duplicates:

  • Today: each issue reads "Cannot modify thinking blocks". Triage opens each, reads each, manually groups. Approximate cost: hours.
  • With this feature: a triage script bucketed by saved_during. Anthropic ships fixes against the heaviest bucket first.

</details>

Additional Context

<details>
<summary><b>Why this should NOT be closed or dismissed</b> (head-on)</summary>

This issue is at structural risk of three dismissal patterns. Addressing them up front:

1. "Just fix the underlying triggers."
Anthropic has already shipped a fix for one (v2.1.156, "Opus 4.8 thinking blocks were modified"). It does not cover the remote-control relay path (rondoallaturka c#93, 2026-05-29 — still wedges on first turn on 2.1.156). Whack-a-mole on individual triggers cannot keep up while users continue to file opaque-error duplicates. The annotation field is a backstop that pays off regardless of fix cadence.

2. "This is observability and we have OTEL."
OTEL traces don't reach end-user error messages and don't reach community bug reports. The proposal is: surface the trigger in the user-facing error and in the on-disk JSONL the user attaches to their bug report — places OTEL doesn't help. Per #48042 (closed/stale), pure-observability framings don't carry the day on this repo; this one is structured differently.

3. "Tell users to enable verbose / --debug mode."
ryanleedavies (c#88) had to run claude --debug to extract the trigger context, then write a 970-line trace into their bug report. Asking ordinary users to run --debug for daily usage is not realistic — the noise is unmanageable. The whole point of save-time annotation is that it's silent in normal use and only consulted at error-translation time.

Impact evidence (the parts not covered above)

evnchn (the account holder) flagged that #10199 is being externally cited as a signal of upstream-coordination concerns. I verified via gh api repos/anthropics/claude-code/issues/10199/timeline and found four tiers of externally-visible damage:

  • Community trackers ranking it at the top. zx0828/big_model_radar#75 (2026-05-29) and #77 (2026-05-30) — daily Chinese-language AI CLI tools digests — both rank #10199 at the top of "Top 10 Community Hot Issues", explicitly noting v2.1.154 "introduced a serious 'thinking block modification' regression, causing many users' sessions to crash".
  • Third-party vendor shipping a permanent workaround feature against the bug. ding113/claude-code-hub#576 is a merged PR titled "feat: thinking signature rectifier" on a Claude-Code-proxy product that added an enableThinkingSignatureRectifier settings toggle (default ON), explicitly citing this repo's #10199 in its PR description. Database migration and all.
  • Ecosystem contamination. lbjlaq/Antigravity-Manager#62 and caozhiyuan/copilot-api#63 are downstream tools whose users now believe those tools are broken because they hit this bug via cross-tool sessions.
  • Visible duplicate pile. 56 internal anthropics/claude-code issues cross-reference #10199 on its GitHub timeline; the May 28 – Jun 1 burst alone produced 30+ near-identical filings. Visually this reads as "one bug spawning 50+ duplicates" — itself the optics story.

evnchn's framing of the meta-pattern, softened from the original wording: "the same opaque outcome from N distinct underlying causes is the opposite of distinct-error-code-per-error common sense; if all kernel panics returned the same stopcode, no one would ship the kernel." The annotation field is the per-error context the harness already has and chooses not to preserve.

What I'm explicitly NOT asking for
  • Not asking for an API change (the API can't know the client-side trigger).
  • Not asking for new logging infrastructure (the field rides on existing JSONL).
  • Not asking for retroactive annotation of historical sessions.
  • Not asking for automatic recovery (separate concern; well covered by holgergevers-hub c#85 §7).
  • Not asking for a single grand bug-fix PR — this is a triage tool that survives whatever the bug-fix cadence is.

</details>

---

If this issue gets auto-closed as a duplicate of #10199 or one of its 56 cross-references: please re-read — this is not a symptom report, it's a request for the annotation field that would have prevented those 56 issues from being separate filings in the first place.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗