Skip to content

Regression: 0.3.152+ emits signature-only empty thinking blocks in successful tool-use turns #337

@natebransc

Description

@natebransc

Summary

@anthropic-ai/claude-agent-sdk appears to have regressed after 0.3.150: successful tool-use turns can emit a completed assistant thinking block whose thinking text is empty, while the raw stream only contains a signature_delta and no thinking_delta text.

This reproduces with AWS Bedrock and Claude Code runtimes packaged by the Agent SDK. It affects SDK consumers that render or persist partial assistant events/transcripts because the SDK output contains a signed, completed thinking block with no displayable content.

Expected behavior

For a successful turn with no displayable thinking text, one of these contracts should hold:

  1. No assistant thinking block is emitted at all, or
  2. A non-displayable/redacted metadata block is emitted in a way consumers can distinguish from visible thinking, or
  3. The SDK documentation explicitly states that signature-only thinking blocks with thinking: "" are valid and must be suppressed by renderers/transcript consumers.

If thinking: { type: "adaptive", display: "summarized" } is requested and a summarized thinking block is emitted, I would expect non-empty thinking_delta text or no visible thinking block.

Actual behavior

In 0.3.152+, successful tool-use turns can produce this raw-event shape:

{"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":"","signature":""}}
{"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"..."}}
{"type":"content_block_stop","index":0}

The completed assistant message then contains:

{"type":"thinking","thinking":"","signature":"..."}

There are zero thinking_delta events and zero displayable thinking characters.

Environment

  • SDK package: @anthropic-ai/claude-agent-sdk
  • Provider: AWS Bedrock
  • Region: us-east-1
  • Models tested:
    • us.anthropic.claude-opus-4-7[1m]
    • us.anthropic.claude-opus-4-8[1m]
  • Node: v22.22.3
  • npm: 10.9.8
  • Local OS used for repro: macOS arm64
  • Repro options:
    • thinking: { type: "adaptive", display: "summarized" }
    • includePartialMessages: true
    • permissionMode: "bypassPermissions"
    • allowedTools: ["Bash"]

Reproduction script shape

This is the probe shape used for the matrices below. The important part is counting raw SDKPartialAssistantMessage.event records before any application-level rendering or transcript processing.

import { query } from "@anthropic-ai/claude-agent-sdk";

const prompt = "Use the Bash tool exactly once to run `printf eval-alpha`, then reply with only DONE.";

const stats = {
  rawThinkingStarts: 0,
  rawThinkingDeltaEvents: 0,
  rawThinkingDeltaChars: 0,
  rawSignatureDeltas: 0,
  finalThinkingBlocks: 0,
  emptyFinalThinkingBlocks: 0,
  finalThinkingChars: 0,
  reportedModels: [],
  resultSubtype: null,
};

for await (const msg of query({
  prompt,
  options: {
    model: "us.anthropic.claude-opus-4-7[1m]",
    thinking: { type: "adaptive", display: "summarized" },
    includePartialMessages: true,
    maxTurns: 3,
    allowedTools: ["Bash"],
    permissionMode: "bypassPermissions",
    allowDangerouslySkipPermissions: true,
  },
})) {
  if (msg.type === "stream_event") {
    const event = msg.event;
    if (event?.type === "content_block_start" && event.content_block?.type === "thinking") {
      stats.rawThinkingStarts += 1;
    }
    if (event?.type === "content_block_delta") {
      const delta = event.delta;
      if (delta?.type === "thinking_delta") {
        stats.rawThinkingDeltaEvents += 1;
        stats.rawThinkingDeltaChars += typeof delta.thinking === "string" ? delta.thinking.length : 0;
      }
      if (delta?.type === "signature_delta") stats.rawSignatureDeltas += 1;
    }
    if (event?.message?.model && !stats.reportedModels.includes(event.message.model)) {
      stats.reportedModels.push(event.message.model);
    }
  }

  if (msg.type === "assistant") {
    for (const block of msg.message?.content ?? []) {
      if (block?.type === "thinking") {
        stats.finalThinkingBlocks += 1;
        const text = typeof block.thinking === "string" ? block.thinking : "";
        stats.finalThinkingChars += text.length;
        if (!text.trim()) stats.emptyFinalThinkingBlocks += 1;
      }
    }
  }

  if (msg.type === "result") stats.resultSubtype = msg.subtype;
}

console.log(stats);

Formal proof 1: this is a runtime-version regression

Definitions:

  • A displayable thinking block exists iff either raw stream output contains at least one thinking_delta with non-empty delta.thinking, or the completed assistant message contains a thinking block whose thinking.trim().length > 0.
  • A signature-only empty thinking block exists iff the raw stream emits a thinking block start and a signature_delta, but emits zero thinking_delta characters, and the completed assistant message contains thinking: "".
  • The application consumer cannot display text that is absent from SDKPartialAssistantMessage.event and absent from the completed assistant message.

Observed with the same prompt, provider, model family, and SDK options:

SDK / Claude Code Requested model Result Raw thinking_delta chars Empty final thinking blocks Conclusion
0.3.150 / 2.1.150 us.anthropic.claude-opus-4-7[1m] success 96 0 Displayable thinking exists.
0.3.152 / 2.1.152 us.anthropic.claude-opus-4-7[1m] success 0 1 Signature-only empty thinking block.
0.3.153 / 2.1.153 us.anthropic.claude-opus-4-7[1m] success 0 1 Signature-only empty thinking block.
0.3.156 / 2.1.156 us.anthropic.claude-opus-4-7[1m] success 0 1 Signature-only empty thinking block.
0.3.157 / 2.1.157 us.anthropic.claude-opus-4-7[1m] success 0 1 Signature-only empty thinking block.

Therefore, between 0.3.150 and 0.3.152, successful tool-use turns started producing signed empty thinking blocks instead of displayable summarized thinking for the same observable SDK contract.

Note: 0.3.151 is not published in the npm registry, so the first published post-0.3.150 candidate I could test was 0.3.152.

Formal proof 2: the issue follows runtime version, not AWS credentials/account

I also crossed two AWS Bedrock credential sets with old/new SDK runtimes using the same five safe Bash tool-use prompts: echo_alpha, math_node, pwd_check, two_commands, list_count.

Each run used:

  • us.anthropic.claude-opus-4-7[1m]
  • thinking: { type: "adaptive", display: "summarized" }
  • includePartialMessages: true
  • permissionMode: "bypassPermissions"
Runtime Credential set Successes Thinking blocks Empty thinking blocks Non-empty thinking chars Signature-only thinking deltas
0.3.153 / 2.1.153 A 5/5 5 5 0 5
0.3.150 / 2.1.150 B 5/5 4 0 846 4
0.3.150 / 2.1.150 A 5/5 5 0 2,362 5
0.3.153 / 2.1.153 B 5/5 5 5 0 5

Since 0.3.150 produced non-empty thinking on both credential sets, and 0.3.153 produced empty thinking on both credential sets, the observed behavior follows SDK/Claude Code runtime version rather than the AWS credential set.

Formal proof 3: Opus 4.8 does not eliminate the issue on newer runtimes

I tested us.anthropic.claude-opus-4-8[1m], which the stream reported as claude-opus-4-8.

SDK / Claude Code Requested model Result Raw thinking_delta chars Empty final thinking blocks Notes
0.3.150 / 2.1.150 us.anthropic.claude-opus-4-8[1m] 5/5 successes 67 0 Can call 4.8; when thinking was emitted, it was non-empty.
0.3.154 / 2.1.154 us.anthropic.claude-opus-4-8[1m] 5/5 successes 0 5 Latest produced empty thinking blocks.
0.3.156 / 2.1.156 us.anthropic.claude-opus-4-8[1m] success 0 1 next still reproduced the issue.
0.3.157 / 2.1.157 us.anthropic.claude-opus-4-8[1m] success 0 1 May 29 latest/next still reproduced the issue in a local Bedrock raw-stream probe.

A single-prompt variant sweep on 0.3.154 + 4.8[1m] also reproduced the empty signature-only shape for:

  • thinking: { type: "adaptive" }
  • thinking: { type: "adaptive", display: "summarized" }
  • thinking: { type: "adaptive", display: "omitted" }
  • thinking: { type: "enabled", budgetTokens: 1024, display: "summarized" }

thinking: { type: "disabled" } produced no thinking block, as expected.

Five-prompt repeat on May 29, 2026 with us.anthropic.claude-opus-4-8[1m]:

Provider SDK / Claude Code Successes Thinking blocks Empty thinking blocks Raw thinking_delta chars Raw signature deltas
AWS Bedrock 0.3.156 / 2.1.156 5/5 5 5 0 5
AWS Bedrock 0.3.157 / 2.1.157 5/5 5 5 0 5

Formal proof 4: the data is absent at the SDK raw-event boundary

The SDK type for partial streaming messages is:

type SDKPartialAssistantMessage = {
  type: "stream_event";
  event: BetaRawMessageStreamEvent;
  // ...
};

For affected runs, SDKPartialAssistantMessage.event contains a thinking block start and a signature_delta, but no thinking_delta text. The final assistant message also contains a thinking block with thinking: "".

Therefore the missing displayable text is already absent at the earliest SDK event boundary available to consumers. A renderer/transcript consumer downstream of the SDK cannot recover the thinking text.

This does not prove whether the native Claude Code binary is faithfully forwarding a provider response or applying an internal transform before surfacing SDK events. It does prove that SDK consumers receive a signed empty visible-thinking block in successful turns.

Formal proof 5: direct Anthropic provider did not reproduce the issue

I also tested the same raw SDK event boundary with Bedrock disabled and the direct Anthropic provider. This used local Claude Code auth rather than ANTHROPIC_API_KEY in the shell environment.

Single-prompt matrix, same safe Bash tool-use prompt and thinking: { type: "adaptive", display: "summarized" }:

Provider SDK / Claude Code Requested model Result Raw thinking_delta chars Empty final thinking blocks
Direct Anthropic 0.3.150 / 2.1.150 claude-opus-4-7 success 103 0
Direct Anthropic 0.3.150 / 2.1.150 claude-opus-4-8 success 100 0
Direct Anthropic 0.3.152 / 2.1.152 claude-opus-4-7 success 103 0
Direct Anthropic 0.3.152 / 2.1.152 claude-opus-4-8 success 100 0
Direct Anthropic 0.3.153 / 2.1.153 claude-opus-4-7 success 156 0
Direct Anthropic 0.3.153 / 2.1.153 claude-opus-4-8 success 100 0
Direct Anthropic 0.3.154 / 2.1.154 claude-opus-4-7 success 108 0
Direct Anthropic 0.3.154 / 2.1.154 claude-opus-4-8 success 100 0
Direct Anthropic 0.3.156 / 2.1.156 claude-opus-4-7 success 172 0
Direct Anthropic 0.3.156 / 2.1.156 claude-opus-4-8 success 100 0
Direct Anthropic 0.3.157 / 2.1.157 claude-opus-4-7 success 108 0
Direct Anthropic 0.3.157 / 2.1.157 claude-opus-4-8 success 108 0

Five-prompt direct-provider controls against the Bedrock failure cases:

Provider SDK / Claude Code Requested model Successes Thinking blocks Empty thinking blocks Raw thinking_delta chars Raw signature deltas
Direct Anthropic 0.3.153 / 2.1.153 claude-opus-4-7 5/5 5 0 512 5
Direct Anthropic 0.3.154 / 2.1.154 claude-opus-4-8 5/5 5 0 546 5
Direct Anthropic 0.3.156 / 2.1.156 claude-opus-4-8 5/5 5 0 618 5
Direct Anthropic 0.3.157 / 2.1.157 claude-opus-4-8 5/5 5 0 530 5

Conclusion: the empty signature-only thinking issue did not reproduce on the direct Anthropic provider in this matrix. The currently observed failure appears specific to the Bedrock provider path in Claude Code/Agent SDK 0.3.152+, including 0.3.156 and 0.3.157 in local raw-stream probes; the same SDK versions and prompts produced non-empty thinking on direct Anthropic.

Impact

SDK consumers that render or persist assistant thinking blocks need to special-case signed empty thinking blocks. Without that special case, UIs can show completed empty reasoning sections and transcripts can contain completed assistant thinking blocks with no displayable content.

Request

Can you clarify whether signature-only thinking blocks with thinking: "" are intentional SDK output?

If intentional, please document the contract and recommended consumer behavior. If unintentional, please either restore non-empty summarized thinking output for display: "summarized" or suppress visible thinking blocks when no thinking_delta text is available.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions