{"record":{"id":"8e9318aa665e8c28","repo":"CopilotKit/CopilotKit","slug":"tool-message-must-have-a-toolcallid","errorCode":null,"errorMessage":"Tool message must have a toolCallId","messagePattern":"Tool message must have a toolCallId","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/runtime-client-gql/src/message-conversion/agui-to-gql.ts","lineNumber":259,"sourceCode":"    id: toolCall.id,\n    name: toolCall.function.name,\n    arguments: argumentsObj,\n    parentMessageId: parentMessageId,\n  });\n}\n\nexport function aguiToolMessageToGQLResultMessage(\n  message: agui.Message,\n  toolCallNames: Record<string, string>,\n): gql.ResultMessage {\n  if (message.role !== \"tool\") {\n    throw new Error(\n      `Cannot convert message with role ${message.role} to ResultMessage`,\n    );\n  }\n\n  if (!message.toolCallId) {\n    throw new Error(\"Tool message must have a toolCallId\");\n  }\n\n  const actionName = toolCallNames[message.toolCallId] || \"unknown\";\n\n  // Handle result content - it could be a string or an object that needs serialization\n  let resultContent: string;\n  const messageContent = message.content || \"\";\n\n  if (typeof messageContent === \"string\") {\n    // Expected case: content is already a string\n    resultContent = messageContent;\n  } else if (typeof messageContent === \"object\" && messageContent !== null) {\n    // Handle case where content is an object that needs to be serialized\n    try {\n      resultContent = JSON.stringify(messageContent);\n    } catch (error) {\n      console.warn(`Failed to stringify tool result for ${actionName}:`, error);\n      resultContent = String(messageContent);","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/CopilotKit/CopilotKit/blob/68fbe97d87420bd84e8196965c71bd6e394a3f7a/packages/runtime-client-gql/src/message-conversion/agui-to-gql.ts#L241-L277","documentation":"Thrown when converting an AG-UI message with role \"tool\" into a GraphQL ResultMessage but toolCallId is falsy. ResultMessage requires actionExecutionId to correlate the tool result with the originating tool call; without it the result cannot be matched. This is a malformed tool message, not a transient failure.","triggerScenarios":"A tool message object created without a toolCallId (e.g. { role: \"tool\", content: \"...\" }); toolCallId set to empty string, undefined, or null; deserializing AG-UI messages from JSON where the field was dropped or camelCased incorrectly.","commonSituations":"Hand-built tool result messages in custom agent bridges; LLM/agent runtimes that emit tool outputs without echoing the call ID; data migration between message schemas that renamed toolCallId (e.g. tool_call_id).","solutions":["Set toolCallId on the tool message, matching the id of the corresponding ToolCall / action execution","If consuming from another runtime, map its call-id field (tool_call_id / actionExecutionId) to toolCallId before conversion","Validate messages against the AG-UI Message schema before conversion"],"exampleFix":"// before\nconst msg = { id: \"1\", role: \"tool\", content: \"42\" };\n\n// after\nconst msg = { id: \"1\", role: \"tool\", content: \"42\", toolCallId: \"call-abc\" };","handlingStrategy":"validation","validationCode":"if (msg.role === \"tool\" && !msg.toolCallId) {\n  console.warn(\"Dropping tool message without toolCallId\", msg.id);\n  continue;\n}","typeGuard":"function isCompleteToolMessage(m: agui.Message): m is Extract<agui.Message, { role: \"tool\"; toolCallId: string }> {\n  return m.role === \"tool\" && typeof m.toolCallId === \"string\" && m.toolCallId.length > 0;\n}","tryCatchPattern":"try {\n  const rm = aguiToolMessageToGQLResultMessage(msg, names);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"toolCallId\")) { /* drop or re-link message */ }\n  else throw e;\n}","preventionTips":["Always pair tool results with the originating ToolCall id","Validate inbound messages against the AG-UI schema before conversion"],"tags":["agui-protocol","tool-call","message-conversion","missing-field"],"backgroundTag":"missing-tool-call-id","analyzedSha":"68fbe97d87420bd84e8196965c71bd6e394a3f7a","analyzedAt":"2026-08-27T04:03:26.537Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}