From 35c20f3e6e008970ccdc9ea460682c578bd6d35a Mon Sep 17 00:00:00 2001 From: pdfinn Date: Tue, 24 Feb 2026 13:53:47 +0700 Subject: [PATCH] fix(client): replace empty text content block with placeholder When the LLM returns an end_turn with no text content after a tool call, the session stores Message{Content:"", StructuredContent:""}. On the next user turn, buildMessageParam() hit the plain-text branch and called NewTextBlock(""), which the Anthropic API rejects with: 400: "messages: text content blocks must be non-empty" Replace empty Content with "..." before building the text block. This preserves the alternating user/assistant message structure required by the API without introducing invalid empty blocks. Co-Authored-By: Claude Sonnet 4.6 --- internal/llm/client.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/internal/llm/client.go b/internal/llm/client.go index fece21f986e4daba30f83b1a42ec2ed9cf107c40..66f2dd523209a2949eb07382ecc3490bcb064f0a 100644 --- a/internal/llm/client.go +++ b/internal/llm/client.go @@ -769,11 +769,17 @@ func buildMessageParam(msg Message) anthropic.MessageParam { } if msg.StructuredContent == "" { - // Plain text message. + // Plain text message. Guard against empty text blocks — the Anthropic API + // rejects any content block with text:"". This can happen when an assistant + // end_turn after tool results has no text (Claude acknowledged silently). + content := msg.Content + if content == "" { + content = "..." + } if role == anthropic.MessageParamRoleUser { - return anthropic.NewUserMessage(anthropic.NewTextBlock(msg.Content)) + return anthropic.NewUserMessage(anthropic.NewTextBlock(content)) } - return anthropic.NewAssistantMessage(anthropic.NewTextBlock(msg.Content)) + return anthropic.NewAssistantMessage(anthropic.NewTextBlock(content)) } // Structured content: unmarshal and rebuild content blocks.