anthropics / anthropics/claude-agent-sdk-typescript
Regression: 0.3.152+ emits signature-only empty thinking blocks in successful tool-use turns
- Ngôn ngữ chính
- Shell
- Star
- 1.8k
- Fork
- 226
- Chỉ số merge pull request
- Không có pull request nào được merge trong 30 ngày
Mô tả
## 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:
```json
{"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:
```json
{"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.
```js
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:
```ts
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.
Hướng dẫn đóng góp
Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này
Đánh giá
Issue này chưa được đánh giá.