{"record":{"id":"bae5f095f26cef7d","repo":"wavetermdev/waveterm","slug":"toolresults-cannot-be-empty","errorCode":null,"errorMessage":"toolResults cannot be empty","messagePattern":"toolResults cannot be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/aiusechat/anthropic/anthropic-convertmessage.go","lineNumber":750,"sourceCode":"func convertMessageForAPI(msg anthropicInputMessage) anthropicInputMessage {\n\t// Create a copy of the message\n\tconverted := anthropicInputMessage{\n\t\tRole:    msg.Role,\n\t\tContent: make([]anthropicMessageContentBlock, len(msg.Content)),\n\t}\n\n\t// Copy each content block and clean it (strips internal fields)\n\tfor i, block := range msg.Content {\n\t\tconverted.Content[i] = *block.Clean()\n\t}\n\n\treturn converted\n}\n\n// ConvertToolResultsToAnthropicChatMessage converts AIToolResult slice to anthropicChatMessage\nfunc ConvertToolResultsToAnthropicChatMessage(toolResults []uctypes.AIToolResult) (*anthropicChatMessage, error) {\n\tif len(toolResults) == 0 {\n\t\treturn nil, errors.New(\"toolResults cannot be empty\")\n\t}\n\n\tvar contentBlocks []anthropicMessageContentBlock\n\n\tfor _, result := range toolResults {\n\t\tif result.ToolUseID == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"tool result missing ToolUseID\")\n\t\t}\n\n\t\tvar content interface{}\n\t\tvar isError bool\n\n\t\tif result.ErrorText != \"\" {\n\t\t\tcontent = result.ErrorText\n\t\t\tisError = true\n\t\t} else {\n\t\t\t// Check if text looks like an image data URL\n\t\t\tif strings.HasPrefix(result.Text, \"data:image/\") {","sourceCodeStart":732,"sourceCodeEnd":768,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/aiusechat/anthropic/anthropic-convertmessage.go#L732-L768","documentation":"ConvertToolResultsToAnthropicChatMessage converts a slice of AIToolResult into an Anthropic chat message containing tool_result content blocks. The library rejects an empty slice because Anthropic API messages must carry at least one content block; there is nothing meaningful to send for zero tool results. The caller must guarantee at least one tool result before invoking this converter.","triggerScenarios":"Calling ConvertToolResultsToAnthropicChatMessage(nil) or ConvertToolResultsToAnthropicChatMessage([]uctypes.AIToolResult{}) — i.e. the len(toolResults) == 0 check at line 750. This happens when a tool-execution loop produced no results (e.g. all tool calls were filtered out or execution was skipped) but the code still proceeds to convert.","commonSituations":"A chat step ends with pending tool calls that were never executed; a batching layer collects results into a slice that ends up empty after deduplication/filtering; tests pass an empty slice; an early error path skipped tool execution but conversion is still attempted.","solutions":["Guard the call site: only call ConvertToolResultsToAnthropicChatMessage when len(toolResults) > 0","If no tool results exist, skip the tool-result turn entirely and send the next user/assistant message instead","If tool calls were made but not executed, run them (or synthesize tool_result blocks with an error payload) so the slice is non-empty","Check upstream logic that builds the toolResults slice for early-return or filtering bugs that drop all entries"],"exampleFix":"// before\nmsg, err := ConvertToolResultsToAnthropicChatMessage(toolResults)\n// after\nif len(toolResults) == 0 {\n    return nil // nothing to send; skip tool-result turn\n}\nmsg, err := ConvertToolResultsToAnthropicChatMessage(toolResults)","handlingStrategy":"validation","validationCode":"if len(toolResults) == 0 {\n    // skip the tool-result turn entirely\n    return nil\n}\nmsg, err := ConvertToolResultsToAnthropicChatMessage(toolResults)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never call converters with nil/empty slices; skip the turn instead","When tool calls exist but fail to execute, synthesize error tool_result blocks so results are non-empty","Review filtering logic that builds the toolResults slice for cases that drop every entry"],"tags":["anthropic","tool-results","validation","message-conversion"],"backgroundTag":"empty-input-validation","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}