Tracking Down "No Response from the 10th Call" in the Bedrock Converse API — A Pitfall When Combining Extended Thinking with Tool Use
This page has been translated by machine translation. View original
TL;DR — Notes on Extended Thinking × Tool Use
This is a summary for those who aren't interested in the investigation process and just want the conclusion.
Problem: When using Extended Thinking (reasoningContent) together with tool use in the Bedrock Converse API, a ValidationException occurs if reasoningContent blocks become consecutive as a result of processing the conversation history.
Cause: reasoningContent blocks carry a cryptographic signature (signature). This signature proves the authenticity of the block (that it was generated by Claude); it is not a hash of the text content. In addition to the authenticity of the signature, the API structurally verifies whether the consecutive pattern of reasoning blocks matches the model's original output. When toolUse/toolResult blocks are excluded from the conversation history, reasoningContent blocks that were originally non-consecutive become adjacent, creating a consecutive pattern that doesn't exist in the original output, and the structural verification fails.
モデル出力: [reasoning_A, toolUse, reasoning_B, text]
↓ toolUse除外
フィルタ後: [reasoning_A, reasoning_B, text]
^^^^^^^^^^^^^^^^^^^^^^^^
モデル出力には存在しない連続パターン → ValidationException
Countermeasures:
- If
reasoningContentblocks become consecutive after filtering, merge their text into a single block (the signature doesn't verify the text content, so keeping either signature is fine) - Exclude messages whose
contentbecomes empty after filtering from the conversation history (to prevent cascade failure)
Reference: Anthropic official documentation — Extended thinking
the entire sequence of consecutive thinking blocks must match the outputs generated by the model during the original request
Introduction
"After asking the chat assistant about 10 questions, it stopped responding from the 11th question. No error is displayed."
When I received this report, my first hypothesis was "context window exceeded." I assumed that after 10 exchanges the token limit had been reached and an input-too-long error was occurring.
To cut to the conclusion, this hypothesis was wrong.
The actual cause was a violation of message structure constraints in the Bedrock Converse API, and getting there required an investigation spanning multiple data sources: CloudWatch, DynamoDB, Bedrock Model Invocation Logging, and direct API calls. In this article, I'll walk through that investigation process step by step.
Summary of Symptoms
The application where the problem occurred is a chat assistant using the Bedrock Converse API. It runs with the following configuration:
- Model: Claude Sonnet 4 (Extended Thinking enabled)
- Tool use: Tools such as database queries via Function Calling
- Conversation history: Stored in DynamoDB, with the full history sent to the API on every request
Reported symptoms:
- Responds normally up to about 10 questions
- From the 11th question onward, no error is displayed and the app immediately returns to a state where the next question can be sent
- No application crash or error screen
The fact that "no error appears" was what made this tricky.
Chapter 1: Discovering the Silent Error
Nothing in CloudWatch
First, I checked CloudWatch Logs. I searched the application's log group for the relevant time period, but there were no logs at WARN level (level 40) or above.
fields @timestamp, @message
| filter level >= 40
| sort @timestamp desc
| limit 100
Result: 0 hits. All logs were INFO (level 30) only.

A clue in DynamoDB
Next, I checked the chat history table in DynamoDB. This application stores chat messages encoded with gzip compression + Base64. When decoded:
[
{ "type": "note", "key": "InternalServerError" },
{ "type": "note", "key": "InternalServerError" },
{ "type": "note", "key": "InternalServerError" }
]
The error was saved in DynamoDB but never sent to the client.
Checking the code, I found a problem in the error handling for errors that occur while processing a streaming response. Although the error information was saved to the DB, both the SSE transmission to the client and the log output were missing.
This is a pitfall specific to stream processing. In an ordinary request/response, you can simply return the error as an HTTP status code, but an error during SSE streaming occurs "after the response has already started being returned," so dedicated handling to notify about the error is required. Because this notification handling was missing, the catch block swallowed the error, and it never reached the outer error handler either.

Lessons at this point
- Errors not being visible ≠ errors not occurring: Only by directly checking the data in the persistence layer could I confirm that the error existed
- Silent catch blocks are dangerous: If you catch an error, you must always do both log output and user notification
Chapter 2: The Identity of the ValidationException
When I found InternalServerError in DynamoDB, I still suspected "context window exceeded." However, a rough estimate of the token count of the saved messages showed that only about 15% of the 200K token limit was being used. I needed to look for another cause.
| Item | Size |
|---|---|
| System prompt | About 10,000 characters |
| Total text content | About 26,000 characters |
| Total reasoning text | About 7,000 characters |
| Total reasoning signatures | About 19,000 characters |
The total is about 62,000 characters (≒ 20,000–30,000 tokens). The model used is Claude Sonnet 4.6 (context window of 200K tokens), and it turned out that only about 15% of the limit was being used.
No error details were output in the application logs at all, and DynamoDB only stored the code InternalServerError. To identify the error, I enabled Bedrock Model Invocation Logging.
Enabling Bedrock Model Invocation Logging
-
Create a CloudWatch Logs log group: Retention period of 1 day (for temporary debugging)

-
Enable it in Bedrock settings: Amazon Bedrock → Settings → Model invocation logging

-
Select the logging destination: Select CloudWatch Logs only

-
Create an IAM role: A service role for Bedrock to write logs

-
Check in CloudWatch: Select the log group you created in CloudWatch and check the logs

The error type revealed
After reproducing the issue in the staging environment, I checked the Invocation Log:
{
"operation": "ConverseStream",
"modelId": "jp.anthropic.claude-sonnet-4-6",
"errorCode": "ValidationException"
}
It was not InternalServerError but ValidationException. InternalServerError was a code assigned by the application's catch block; the actual error type returned by the Bedrock API was ValidationException, which indicates a violation of request structure constraints.
Unfortunately, Bedrock Model Invocation Logging does not record the request body or error message details when an error occurs. However, the body of the immediately preceding successful request is fully recorded, so I proceeded with the investigation using it as a clue.
Chapter 3: Root Cause — Consecutive Reasoning Blocks
Filtering the conversation history
First, some background. When sending the conversation history to the Bedrock API, this application selected only the content types that needed to be sent to the API (text, image, attachment, reasoning) using an allowlist approach.
This allowlist was designed at a time when the application did not yet have tool-use functionality. At that time only text and attachment existed, so an allowlist was sufficient. When reasoning (Extended Thinking) and image were added later, they were added to the list, but the tool block, introduced afterwards, was left off the allowlist.

The tool block is metadata for UI display that holds the tool execution state inside the application (tool name, parameters, results), and it doesn't need to be sent to the Bedrock API, so not being on the allowlist is normally not a problem.
However, this design had an unexpected side effect.
Differences between the successful request and the stored data
By comparing the successful request (Invocation Log) with the stored messages (DynamoDB), I made a decisive discovery.
With allowlist filtering, this works fine in most cases. However, when the model "rethinks" between tool calls — for example, inserting reasoning after seeing a tool execution result and before calling another tool — the tool block can be the only separator between reasoning blocks:

Verifying the hypothesis with the AWS CLI
The analysis up to this point gave rise to the hypothesis that "consecutive reasoning blocks cause an error." However, detailed error messages can't be obtained from Bedrock Model Invocation Logging. To confirm the hypothesis, I sent test payloads directly to the Bedrock Converse API with the AWS CLI to verify it.
As the base for the tests, I used the payload of the last successful request obtained from Invocation Logging. This payload contains actual signed reasoning blocks, so it allows accurate verification of the API's constraints.
aws bedrock-runtime converse \
--region ap-northeast-1 \
--model-id jp.anthropic.claude-sonnet-4-6 \
--cli-input-json file://test-payload.json
I ran four tests and obtained the following results:
| Test | Payload content | Result |
|---|---|---|
| Baseline | The successful request as-is (tool blocks already excluded, reasoning non-consecutive) | Success |
| Test 1 | Make reasoning blocks consecutive (delete the text in between) | ValidationException |
| Test 2 | Make reasoning consecutive in a past assistant message (not the latest) | ValidationException |
| Test 3 | Set the assistant message's content to an empty array |
ValidationException |
Result of Test 1:
An error occurred (ValidationException) when calling the Converse operation:
The model returned the following errors:
messages.1.content.1: `thinking` or `redacted_thinking` blocks in the
latest assistant message cannot be modified. These blocks must remain
as they were in the original response.
From comparing the baseline and Test 1, it was confirmed that a shift in block position (index) is not itself a problem, and that an error occurs only when reasoning blocks are adjacent.
From the result of Test 2, I learned that the API validates all assistant messages in the conversation history, not just the latest one. In other words, once a message with consecutive reasoning blocks exists in the history, every subsequent request fails.
The identity of the signature — uncovering the API's validation mechanism through experiments
From the error message These blocks must remain as they were in the original response, we can tell that signature verification is involved. However, what exactly the signature verifies isn't clear from the documentation alone.
Anthropic's official documentation contains the following statement:
the entire sequence of consecutive thinking blocks must match the outputs generated by the model during the original request; you cannot rearrange or modify the sequence of these blocks
To find out what this "sequence" refers to — the text content or the structure of the blocks — I ran additional experiments to identify what the signature verifies.
What does the signature verify?
Using the earlier test payload (two consecutive reasoning blocks [reasoning_A, reasoning_B, text]), I ran four additional tests concerning the signature:
| Test | Operation | Result |
|---|---|---|
| Test 4 | Rewrite reasoning_A's text into a completely different sentence, leaving the signature as is | Success |
| Test 5 | Swap the signatures of reasoning_A and reasoning_B (leaving the text as is) | Success |
| Test 6 | Merge the text of the two reasoning blocks into one block, using either signature | Success |
| Test 7 | Use a completely forged signature string | ValidationException |
Error message for Test 7:
messages.1.content.0: Invalid `signature` in `thinking` block
These results clarified the role of the signature:
1. The signature is not a hash of the text content
It succeeded even when the text was completely rewritten in Test 4, and it also succeeded when the signatures were swapped in Test 5. The signature is not tied to the contents of reasoningText.
2. The signature is proof of authenticity that "Claude generated it"
Only the forged signature failed in Test 7. The role of the signature is to prove that the block was generated by the Claude API (an authenticity proof). Conceptually it's close to a JWT (JSON Web Token): it's signed with the server's secret key and verified with the same key.

3. The API is stateless — the signature embeds the "state"
LLM APIs are inherently stateless. How can the API verify "whether it matches the original output" when it doesn't keep the conversation history on the server side? The answer is that the signature itself contains the information required for verification. It's the same mechanism by which a JWT can have its authenticity verified without a server-side session store.

4. Verification of the consecutive pattern is a structural check separate from the signature
The signature verifies "whether this is a block generated by Claude," while verification of the consecutive pattern checks "whether it matches the structure of the model's original output." These are verifications at different layers:
- Signature verification (confirmed in Test 7): Whether the block was generated by the Claude API
- Structural verification (confirmed in Test 1): Whether the consecutive pattern of reasoning blocks matches the model's output

What the test results mean
This finding directly affects the countermeasure. Because the signature does not verify the text content, it is possible to merge the text of consecutive reasoning blocks into a single block (confirmed in Test 6). Since this resolves the consecutiveness without losing the model's thinking context, it's a better countermeasure than simply deleting blocks.
Note that the same documentation permits omitting the thinking blocks of previous turns entirely (except when tool use is involved). The problem is "creating a consecutive pattern that doesn't exist in the original output."
Result of Test 3:
An error occurred (ValidationException) when calling the Converse operation:
The content field in the Message object at messages.1 is empty.
Add a ContentBlock object to the content field and try again.
This also confirmed the mechanism of the cascade failure (details in Chapter 4).

Why does it occur after a certain number of exchanges?
This problem does not occur in every exchange. The trigger is a pattern where only a reasoning block is inserted between tool calls.
For example, when a tool call fails and the model retries:
- reasoning (thinking about the query) → text → tool (executed, failed)
- reasoning (thinking about a fix) → tool (re-executed, failed)
- reasoning (thinking about a further fix) → tool (succeeded) → text (explaining the result)
In step 2, there is no text block between reasoning and tool, so excluding the tool makes the reasoning blocks consecutive.
In early exchanges, text blocks often exist before and after tool blocks, so reasoning doesn't become consecutive after filtering. As the number of exchanges grows, tool retries and compound calls occur, and the probability of this pattern appearing rises. This is the reason for the reproducibility of "from around the 10th time."

Chapter 4: Cascade Failure — Once It Fails, It's Permanently Broken
In addition to the root cause, there is a cascade failure in which once a request fails, all subsequent requests fail permanently. This made the problem even more serious.
This failure pattern is not limited to this case; it can occur in any chat application that persists the conversation history and resends it every time. If an incomplete assistant message is saved when an error occurs, that broken message continues to be included in all subsequent requests.

The mechanism in this case:
- On the first failure, the assistant message is saved in an incomplete state (without any valid content blocks)
- On the next request, all blocks of this message are excluded by the filter, and an assistant message with empty content is sent to Bedrock
- Empty content also causes a
ValidationException→ a permanent failure loop
// After filtering, content becomes empty
{
"role": "assistant",
"content": [] // Violates Bedrock API constraints
}
As confirmed in Test 3 of the previous chapter, an empty content array also returns a ValidationException.
In other words, even if you fix the root cause (consecutive reasoning blocks), chats that failed in the past remain permanently broken. Unless you also handle skipping messages with empty content, existing failed chats will not recover.

Fixes and Countermeasures
Fix ①: Eliminating the silent error
I added log output and client notification to the error handling during stream processing. Errors during SSE streaming need to be reported through a different path than ordinary HTTP error responses, so this is an easy point to overlook.
Fix ②: Eliminating the root cause
In the filtering process used when sending the conversation history to the Bedrock API, the following two points need to be handled.
1. Resolving consecutive reasoning blocks
If reasoning blocks become consecutive after excluding tool blocks, merge the consecutive reasoning blocks into one. As described earlier, the signature doesn't verify the text content, so merging the text and keeping either signature will pass verification. This resolves the consecutive pattern while preserving the model's thinking context.
// Example of resolving consecutive reasoning after excluding tool blocks
function sanitizeContentBlocks(blocks: ContentBlock[]): ContentBlock[] {
const filtered = blocks.filter(b => b.type !== 'toolUse' && b.type !== 'toolResult');
// Merge the text of consecutive reasoning blocks into a single block
const result: ContentBlock[] = [];
for (const block of filtered) {
const prev = result[result.length - 1];
if (prev?.type === 'reasoning' && block.type === 'reasoning') {
prev.reasoningText += '\n\n' + block.reasoningText;
// The signature isn't tied to the text content, so keep the first block's signature as is
} else {
result.push({ ...block });
}
}
return result;
}
2. Skipping messages with empty content
Exclude messages whose content becomes empty after filtering from the conversation history. This prevents cascade failure in chats that failed in the past.
// Example of skipping messages with empty content
const messages = history
.map(msg => ({ ...msg, content: sanitizeContentBlocks(msg.content) }))
.filter(msg => msg.content.length > 0);
Looking Back on the Investigation Process
Here is a summary of the techniques used in this investigation and their effectiveness.
| Technique | What it confirmed | Limitations |
|---|---|---|
| CloudWatch Logs | The absence of logs was itself a clue | The error was caught and no log was output, so there was no direct information |
| DynamoDB | Error code and the overall picture of the message structure | Detailed error messages are not stored |
| Bedrock Model Invocation Logging | The actual error type (ValidationException) and the payload of the successful request |
Request body and error message on failure are not recorded |
| Cross-referencing DynamoDB × code × Invocation Log | Identifying the root cause | — |
| Direct API calls with the AWS CLI (7 patterns) | The exact wording of the error message, confirming the hypothesis, that all messages are validated, and identifying what the signature verifies | — |

The most effective approach was "cross-referencing multiple data sources." A single log source didn't reveal the whole picture; only by combining the data stored in DynamoDB × the successful payload in the Invocation Log × static analysis of the code could I identify the cause.
Summary
For Bedrock Converse API users
- When combining Extended Thinking and tool use, be careful that reasoningContent blocks don't become consecutive when reconstructing the conversation history
- When filtering specific content types from the conversation history, verify that the block order after filtering satisfies the API's constraints
- The
signatureof a reasoning block is not a hash of the text content but proof of authenticity that Claude generated it. Merging or rewriting text is tolerated, but forged signatures and consecutive patterns that don't exist in the original output are rejected
As a debugging technique
- Suspect silent errors: Even when the user can't see an error, error information may remain in the persistence layer
- Reject hypotheses quickly: I should have measured the actual token count and rejected the "context window exceeded" assumption early instead of being fixated on it
- Make use of Bedrock Model Invocation Logging: It's the most direct way to see what actually happens in API calls. It can be enabled temporarily and disabled right away, so it should be actively used when debugging
- Cross-reference multiple data sources: When a single log source isn't enough, analyze across stored data, application logs, and service logs
