{"record":{"id":"fa7e640fbef1fbac","repo":"wavetermdev/waveterm","slug":"tool-result-missing-tooluseid","errorCode":null,"errorMessage":"tool result missing ToolUseID","messagePattern":"tool result missing ToolUseID","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/aiusechat/anthropic/anthropic-convertmessage.go","lineNumber":757,"sourceCode":"\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/\") {\n\t\t\t\t// Parse the data URL to extract media type and base64 data\n\t\t\t\tparts := strings.SplitN(result.Text, \",\", 2)\n\t\t\t\tif len(parts) == 2 {\n\t\t\t\t\t// Extract media type from \"data:image/png;base64\"\n\t\t\t\t\tmediaTypePart := strings.TrimPrefix(parts[0], \"data:\")\n\t\t\t\t\tmediaType := strings.Split(mediaTypePart, \";\")[0]\n","sourceCodeStart":739,"sourceCodeEnd":775,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/aiusechat/anthropic/anthropic-convertmessage.go#L739-L775","documentation":"Each tool result must reference the tool_use block it responds to via ToolUseID. The Anthropic API requires tool_result blocks to carry a tool_use_id; a result with an empty ToolUseID cannot be mapped and conversion aborts before any API call.","triggerScenarios":"Calling ConvertToolResultsToAnthropicChatMessage with a tool result entry where the ToolUseID field is the empty string.","commonSituations":"Constructing tool results manually and forgetting to copy the ID from the assistant's tool_use block; a previous pipeline step dropping the ID; deserialized results missing fields.","solutions":["Populate ToolUseID from the corresponding tool_use block id in the assistant message","When streaming/iterating tool calls, capture each call's ID before executing the tool","Add a pre-conversion check that every result has a non-empty ToolUseID"],"exampleFix":"// before\nresults := []uctypes.ToolResult{{Content: output}}\n// after\nresults := []uctypes.ToolResult{{ToolUseID: assistantToolUseID, Content: output}}","handlingStrategy":"validation","validationCode":"for i, r := range toolResults {\n    if r.ToolUseID == \"\" { return fmt.Errorf(\"toolResults[%d] missing ToolUseID\", i) }\n}","typeGuard":null,"tryCatchPattern":"blocks, err := anthropic.ConvertToolResultsToAnthropicChatMessage(toolResults)\nif err != nil && strings.Contains(err.Error(), \"missing ToolUseID\") {\n    return fmt.Errorf(\"internal: tool executor did not propagate tool_use id\")\n}","preventionTips":["Thread the tool_use id from the assistant message into every result at execution time","Add a unit test asserting ToolUseID is set for each result","Validate results at the tool-executor boundary, not just at conversion"],"tags":["go","anthropic","tool-use","missing-field"],"backgroundTag":"missing-required-argument","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}