`showThinkingSummaries: true` silently no-ops on Opus 4.7 in non-interactive surfaces (VS Code extension, SDK, `--print`): one-line CLI fix or one-line extension fix

Open 💬 10 comments Opened May 16, 2026 by ojura

Summary

~/.claude/settings.json"showThinkingSummaries": true is the documented, official way to surface thinking summaries in the VS Code / Antigravity chat panel. On claude-opus-4-7[1m] and any 4.7+ model, the setting silently no-ops: the API returns thinking content blocks with empty thinking field plus multi-KB signature only, and the webview falls back to its static <div class="thinkingStatic">Thinking</div> stub because its thinking.length > 0 branch can't fire.

Closes the symptom side of: #49902, #49322, #49268, #51131, #49757, #48065, #49739, #33163, #8477, #30958.
Cross-references the failure mode of the env-var workaround: #56984.

Root cause

Two independent special cases in the harness combine to silently drop thinking summaries for IDE users on Opus 4.7:

  1. The CLI binary gates the showThinkingSummariesdisplay="summarized" propagation on interactive mode. Build site at offset 230510599 in the v2.1.142 bundled binary:

``js
if (K3.type !== "disabled") {
if (z.thinkingDisplay === "summarized" || z.thinkingDisplay === "omitted")
K3.display = z.thinkingDisplay;
else if (!T6() && m6().showThinkingSummaries === !0) // <-- branch B
K3.display = "summarized";
}
``

T6() is getIsNonInteractiveSession(). Branch B requires interactive mode. The IDE spawns the CLI subprocess as --print --input-format stream-json --output-format stream-json, which makes T6() === true, so branch B never fires for chat-panel subprocesses. The setting in settings.json is silently ignored in the place it matters most.

Per Anthropic's own Opus 4.7 migration guide, the API default for thinking.display flipped to "omitted" on 4.7, so without explicit display: "summarized", the API returns nothing user-visible.

  1. The IDE doesn't read showThinkingSummaries when constructing its chat-panel spawn. In extension.js's spawnClaude, q.thinking is never set; only q.maxThinkingTokens: Z is. Downstream in jW this lifts to {type: "enabled", budgetTokens: Z} without display. So even if the CLI gate didn't exist, the IDE's own q.thinking.display would be undefined.

The same architectural shape produces both special cases: model-quality knobs that the IDE doesn't persist through settings.json the way model and effortLevel already do.

The four model-quality settings and how each surface handles them

| Setting | settings.json key | Terminal CLI mechanism | IDE chat-panel mechanism |
|---|---|---|---|
| Model | model | /model slash command + /config panel → writeUserSettingsAndPush | "Switch model…" menu → writeUserSettingsAndPush |
| Effort | effortLevel | /effort slash command + /config panel → writeUserSettingsAndPush | "Effort" menu → writeUserSettingsAndPush |
| Thinking on/off | alwaysThinkingEnabled | /config panel "Thinking mode" toggle + Shift+Tab → writeUserSettingsAndPush | "Thinking" menu → context.globalState.update("thinkingLevel", ...) (Antigravity-local, NOT settings.json) |
| Show thinking summaries | showThinkingSummaries | Read at K3 build, honored only via the !getIsNonInteractiveSession() && setting === true gate | Not exposed as a control; not read at spawn time; never propagated to q.thinking.display |

The first two rows have one persistence policy. The last two each have a different one. The asymmetry is the bug.

Proposed fix (uniform rule)

Every model-quality setting that affects the API request shape lives in settings.json. The surface that exposes a UI for it reads from settings.json at construction time and writes back via writeUserSettingsAndPush on user change. The running CLI subprocess gets live updates via applyFlagSettings. There is no parallel globalState for these knobs.

model and effortLevel already follow this rule. To bring the last two rows in line:

  1. IDE spawnClaude builds q.thinking.display from merged settings. In the chat-panel session-builder, read showThinkingSummaries (and alwaysThinkingEnabled) and set q.thinking = {type: "enabled", budgetTokens: Z, display: "summarized"} when both conditions are met. Sibling to where q.model and q.maxThinkingTokens are already set from settings.
  1. IDE "Thinking" menu toggle writes to settings.json, not globalState. Same path "Switch model…" and "Effort" already use: writeUserSettingsAndPush({alwaysThinkingEnabled: ...}). Side benefit: IDE Thinking state syncs to terminal CLI sessions, the same way model and effort already do.
  1. Optionally expose showThinkingSummaries in the IDE menu or /config. Either as a sibling boolean ("Show thinking summaries") or folded into the Thinking toggle (toggle-on writes both alwaysThinkingEnabled: undefined and showThinkingSummaries: true). Either choice respects the uniform rule.
  1. Drop the CLI's !getIsNonInteractiveSession() gate. Independent of the IDE side. Makes the documented setting do what its name says in every CLI invocation context (interactive, --print, SDK, IDE-spawned). After this lands, even an IDE that hadn't done (1)-(3) would still honor showThinkingSummaries: true for chat panel users who set it manually.

CLI gate drop diff:

- else if (!T6() && m6().showThinkingSummaries === !0)
+ else if (m6().showThinkingSummaries === !0)
    K3.display = "summarized";

All four are additive and complementary. (1)+(2)+(3) close the IDE asymmetry; (4) closes the CLI special-case. Neither alone covers the full audience.

Easier escape-hatch workaround (extension-only, ships today in ojura/claude-patches v1.7)

If a smaller change is preferred as a first step, the IDE's SDK-side spawn-args builder can be patched in one line to push --thinking-display summarized whenever a thinkingConfig reaches it:

- if (U.type !== "disabled" && U.display)
-   i.push("--thinking-display", U.display)
+ if (U.type !== "disabled")
+   i.push("--thinking-display", U.display || "summarized")

This is architecturally less clean than the uniform fix (it changes the SDK-builder's "no opinion default" semantics, conflates "caller passed thinkingConfig" with "caller wants display=summarized"), but in practice the IDE chat panel is the only caller of this builder in the bundled extension, so the blast radius matches the uniform fix. End-users on claude-code builds where this issue persists can apply it via the ojura/claude-patches prebuilt at https://github.com/ojura/claude-patches.

Safety against the #56984 failure mode

CLAUDE_CODE_EXTRA_BODY (the env-var workaround per #56984) breaks WebSearch and WebFetch because it force-injects thinking: {type: "adaptive", ...} into every API request including forced-tool-choice sub-calls and incompatible-model sub-calls. The proposed fixes above never touch thinking.type. They only set thinking.display when thinking is otherwise enabled. The CLI's per-request gate (q.type !== "disabled") continues to drop the entire thinking field for forced-tool-use paths and incompatible-model sub-calls.

Empirical verification: see the follow-up comment for the per-source live-captured table across 25 API requests (main turns, /compact, subagent dispatch, WebSearch, WebFetch). All 25 succeeded, no 400s, non-K3 sub-call sources all had thinking: undefined in their requests regardless of fix being active.

Disclosure

Written with Claude Opus 4.7, which currently has its own thinking summaries hidden by this exact bug.

View original on GitHub ↗

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