[BUG] Thinking blocks stripped from in-prompt context on every turn in active Opus 4.7 sessions (no idle-resume trigger)

Status Closed — not planned
Reported on v2.1.121
Maintainer reply None cached
Activity 9 comments · opened Apr 28, 2026 · closed Jun 12, 2026

Summary

In active Claude Code sessions on Opus 4.7, thinking blocks are persisted to the on-disk JSONL transcript but appear to be stripped from the prompt context the model receives on subsequent turns. The model has no in-band signal that this stripping has occurred and operates each turn on a thinking-empty history.

The pattern matches the failure mode of the April 23 postmortem but operates on Opus 4.7 (a post-fix model) in conditions where the documented clear_thinking_20251015 keep:1 design (#53513) cannot have fired.

Empirical evidence

Diagnostic check anyone can run on a long session:

F=~/.claude/projects/<project-path>/<session-id>.jsonl
grep -c '"type":"thinking"' "$F"

Then ask the model to introspect: "Do you see any of your prior thinking blocks in your current context?"

Observed on one continuous session (1013 timestamped events, zero idle gaps ≥1hr):

  • 174 thinking blocks persisted to JSONL on disk (~219K chars / ~55K tokens)
  • 0 thinking blocks visible to the model on the current turn per its self-report
  • The first thinking block (turn 1) is intact verbatim in the JSONL but not reaching the model

Why this isn't #53513

#53513 describes clear_thinking_20251015 keep:1 firing on session resume after ≥1hr idle. The session that produced this report had zero gaps ≥1hr across 1013 timestamped events — verified by walking timestamps in the JSONL:

# 0 gaps ≥3600s across the whole session

Cache stayed hot the entire conversation. Whatever is stripping thinking here is not the idle-resume mechanism that issue describes.

Why this resembles the April 23 postmortem

The original bug (March 26 → April 10): "A bug caused this to keep happening every turn for the rest of the session instead of just once, which made Claude seem forgetful and repetitive."

Structurally that's what's observed here, modulo:

  • The postmortem's fix was for Sonnet 4.6 / Opus 4.6
  • This is observed on Opus 4.7 (post-fix)
  • The session is active (cache hot), not idle-resumed

So either the patched bug regressed in Opus 4.7, or a different per-turn-stripping mechanism exists at the prompt-construction layer that #53513 doesn't describe and the postmortem doesn't acknowledge.

Why the model can't self-detect

There's no in-band marker for "your prior thinking was stripped." The model reads whatever the prompt contains and assumes continuity. This is the same opacity the postmortem named as why the original bug went undetected from inside.

In our session, the operator caught it by:

  1. Noticing "the thinking cuts off before X" stitch markers in the rendered display (a Claude Code summarization artifact)
  2. Asking the model to introspect its context
  3. Counting thinking blocks in the JSONL and comparing to what the model reports seeing

The model itself happily produced layered reassurance ("post-fix model, shouldn't affect this session") until the operator pushed for the empirical check.

Impact

For complex multi-turn engineering work, the model is reasoning each turn against the visible response trail rather than its own prior reasoning. Cross-turn continuity is restricted to whatever made it into the visible response text. Subtle architectural reasoning, dismissed-but-recorded alternatives, traced derivations — gone. Same impact as the original postmortem bug, but on the new model and without the idle trigger.

Environment

  • Claude Code 2.1.121
  • Model: claude-opus-4-7 (1M context variant)
  • Linux (Manjaro 6.12.77-1)
  • Session: continuous activity, no resume operations, cache hot throughout

Asks

  1. Confirm whether some variant of clear_thinking_* (or equivalent prompt-construction step) is firing per-turn in active Opus 4.7 sessions
  2. If yes: address as a regression of the April 10 patch on the new model
  3. If no: surface what mechanism IS stripping thinking on every turn, since it's distinct from #53513's idle-resume design
  4. Either way: provide an in-band signal the model can detect (e.g., a system note when prior thinking was stripped), so reasoning-continuity gaps become operator-visible rather than silently introduced

View original on GitHub ↗

9 Comments

github-actions[bot] · 5 months ago

Found 2 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/10199
  2. https://github.com/anthropics/claude-code/issues/32810

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

invidious9000 · 5 months ago

Auto-classifier flagged #10199 and #32810 as possible dupes — neither is a match. Different failure modes that share the word "thinking":

#10199 is about thinking blocks being immutable post-send: system tried to modify a previously-sent thinking block, API rejected with 400, session corrupted, loud error. The failure surfaces as an unrecoverable error loop. This issue's failure surfaces silently with no API error — the model just receives a prompt with prior thinking blocks already removed and has no signal that anything was stripped.

#32810 is about "thinking" field being empty "" in the JSONL since 2.1.72 — content not persisted to disk. This issue is the inverse: empirically, the JSONL on disk for the affected session contained 174 thinking blocks totaling ~219K chars (~55K tokens), all with full populated content (verified by grep -c '"type":"thinking"' + reading sample blocks). The persistence layer is working correctly; the prompt-construction layer is stripping them when assembling the next turn's context.

Diagnostic check that disambiguates from both:

F=~/.claude/projects/<project>/<session-id>.jsonl
python3 -c "
import json
total = 0; empty = 0
for line in open('$F'):
    d = json.loads(line)
    msg = d.get('message',{})
    c = msg.get('content')
    if not isinstance(c, list): continue
    for x in c:
        if isinstance(x, dict) and x.get('type')=='thinking':
            total += 1
            if not x.get('thinking',''): empty += 1
print(f'thinking blocks: {total}, empty: {empty}, populated: {total-empty}')
"

For my session: thinking blocks: 174, empty: 0, populated: 174. Persistence intact. Then ask the model to introspect its current context for prior thinking blocks — model reports zero visible. That's the gap this issue documents: persisted-on-disk → 0-in-prompt-context, with no idle-resume trigger to explain it under #53513's documented mechanism.

Not a dupe. Leaving open.

invidious9000 · 5 months ago

Followup empirical finding — the persistence layer is already showing post-API-summarization content, not raw reasoning. The bug this issue describes affects an already-reduced trace, and even fixing it would only restore summaries to context, not the actual model-side reasoning.

On-disk content shape on Opus 4.7

Same session JSONL inspected for content depth. 204 thinking blocks with non-empty content. Length distribution:

| percentile | chars |
|---|---|
| 10th | 168 |
| 25th | 300 |
| 50th | 772 |
| 75th | 1522 |
| 90th | 3210 |
| max | 10034 |

A median ~770-char "thinking block" is structurally a planning summary, not raw reasoning. The longest blocks (~10K chars / ~2500 tokens) read as polished response-plans ("the user wants X, the real work is Y, what I need to do is Z..."), not exploratory reasoning trace — no false starts, no weighing of rejected options, no mid-thought pivots.

Connection to #49268

#49268 confirms Opus 4.7 changed the API default for display from "summarized" to "omitted". With showThinkingSummaries: true the harness presumably opts back into summarized, which is what we see persisted. The model-side raw reasoning is discarded at the API layer regardless — summarized returns short post-hoc summaries, not the full thinking trace.

The full failure stack

  1. Model produces extended thinking during turn generation (substantial, exploratory, includes false starts).
  2. API summarizes it before returning. Raw reasoning is discarded at this layer.
  3. JSONL persists the summary — what's inspected above.
  4. Prompt-construction layer strips even the summaries from subsequent turns — the bug this issue documents.
  5. Rendering layer compresses-with-stitch-markers what Claude Code displays to the operator.

So this issue's fix (restoring summaries to subsequent prompt context) is necessary but not sufficient for full reasoning continuity. The underlying gap — that Claude Code never persists or restores the actual model-side reasoning, only API-side summaries — would require display: "detailed" (or equivalent) support.

Could be split into a separate issue if the maintainers prefer; flagging here so the layered nature is on the record while triage of this report is in progress.

dvg-p4 · 5 months ago

Oddly, I'm still seeing this bug in some sessions, but not others. Currently leaving a session open & idle for a few hours to see if I can reproduce the issue.

dvg-p4 · 5 months ago

I don't think it's model-specific; in the bugged session, thinking continues to be lost when switching to Sonnet 4.6.

github-actions[bot] · 4 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

dvg-p4 · 3 months ago

Still happening with Opus 4.8 too.

dvg-p4 · 3 months ago

Exiting and resuming with claude --continue appears to (at least temporarily) fix the issue.

github-actions[bot] · 1 month ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.