{"record":{"id":"2640f04f0611b47b","repo":"decolua/9router","slug":"kiro-tool-name-changed-between-fragments","errorCode":null,"errorMessage":"Kiro tool name changed between fragments","messagePattern":"Kiro tool name changed between fragments","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"open-sse/executors/kiro.js","lineNumber":848,"sourceCode":"        for (const value of values) {\n          const name = typeof value?.name === \"string\" ? value.name.trim() : \"\";\n          if (!name) throw new Error(\"Kiro toolUseEvent is missing a tool name\");\n          let id;\n          if (value.toolUseId == null) {\n            id = `call_${created}_${state.tools.size + 1}`;\n          } else if (typeof value.toolUseId !== \"string\" || !value.toolUseId.trim()) {\n            throw new Error(\"Kiro toolUseEvent has an invalid toolUseId\");\n          } else {\n            id = value.toolUseId;\n          }\n          let tool = state.tools.get(id);\n          if (!tool) {\n            tool = { id, name };\n            state.tools.set(id, tool);\n            state.bufferedToolBytes += encoder.encode(id).byteLength + encoder.encode(name).byteLength + 32;\n            assertToolBufferBound();\n          } else if (tool.name !== name) {\n            throw new Error(\"Kiro tool name changed between fragments\");\n          }\n          appendToolInput(tool, value.input);\n        }\n      } else if (eventType === \"messageStopEvent\") {\n        state.explicitStop = true;\n        const reason = normalizeStopReason(\n          event.payload?.stopReason ?? event.payload?.stop_reason\n        ) || (state.sawToolUse ? \"tool_use\" : \"end_turn\");\n        const merged = mergeStopReason(state.stopReason, reason);\n        if (merged !== state.stopReason) state.terminalProvenance = \"message_stop_event\";\n        state.stopReason = merged;\n      } else if (eventType === \"metadataEvent\" || eventType === \"MetadataEvent\") {\n        const metadata = event.payload?.metadataEvent || event.payload?.metadata || event.payload;\n        const reason = normalizeStopReason(metadata?.stopReason ?? metadata?.stop_reason);\n        if (reason) {\n          state.explicitStop = true;\n          const merged = mergeStopReason(state.stopReason, reason);\n          if (merged !== state.stopReason) state.terminalProvenance = \"metadata_stop_reason\";","sourceCodeStart":830,"sourceCodeEnd":866,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/executors/kiro.js#L830-L866","documentation":"Kiro streams a single tool call as multiple toolUseEvent fragments keyed by toolUseId. When a fragment arrives for an id already registered, the executor requires the name to match the name recorded when the tool was first seen; a mismatch means two different logical tool calls are sharing one id, which would silently merge their inputs into one corrupt call. The executor throws to preserve integrity of the emitted tool_calls.","triggerScenarios":"Two toolUseEvent streams reuse the same toolUseId but carry different 'name' values — e.g. upstream id-collision, a synthesized id (call_<created>_<n>) colliding because toolUseId was absent and fragment order shifted, or an upstream bug renaming the tool mid-call.","commonSituations":"Upstream generating duplicate toolUseIds within one turn; models emitting parallel tool calls with sloppy id allocation; protocol changes splitting name/input differently across fragments; state.toolUseId synthesis (call_created_N) colliding with a real upstream id of the same form.","solutions":["Retry the request once — mid-stream id/name inconsistency is usually a transient upstream fault","Log both the existing tool.name and the incoming fragment name with the shared id to identify whether it's an id collision or a genuine rename","If names differ only by case/whitespace, normalize names before comparison (trim/lowercase) if upstream is inconsistent","If the upstream genuinely reuses ids across distinct calls, namespace ids per call sequence instead of throwing","Check for collisions between synthesized ids (call_<created>_<n>) and real upstream ids; prefix synthesized ids distinctly"],"exampleFix":"// before: throw on any name change for a known id\n} else if (tool.name !== name) {\n  throw new Error('Kiro tool name changed between fragments');\n}\n// after: treat the mismatch as a new call by minting a fresh id\n} else if (tool.name !== name) {\n  const freshId = `${tool.id}_r${state.tools.size}`;\n  tool = { id: freshId, name };\n  state.tools.set(freshId, tool);\n  console.warn(`[Kiro] toolUseId ${id} reused with different name, split into ${freshId}`);\n}","handlingStrategy":"retry","validationCode":"// on the client side, validate that returned tool_call ids are unique before executing\nconst ids = res.choices[0].message.tool_calls?.map(tc => tc.id) ?? [];\nif (new Set(ids).size !== ids.length) console.warn('duplicate tool_call ids returned');","typeGuard":"function fragmentsConsistent(events) {\n  const byId = new Map();\n  for (const ev of events.filter(e => e.type === 'toolUseEvent')) {\n    const prev = byId.get(ev.toolUseId);\n    if (prev && prev.name !== ev.name) return false;\n    byId.set(ev.toolUseId, ev);\n  }\n  return true;\n}","tryCatchPattern":"try {\n  return await streamKiro(req);\n} catch (e) {\n  if (/tool name changed between fragments/.test(e.message) && attempts < 2) return retry(req);\n  throw e;\n}","preventionTips":["Retry once on fragment inconsistency before failing the client turn","If the upstream omits toolUseId, ensure synthesized ids are namespaced so they cannot collide with real upstream ids","Limit aggressive parallel tool-call prompting (parallel tool use) with models known to reuse ids sloppily","Log old vs new name on mismatch to distinguish upstream id collision from genuine rename before changing handling"],"tags":["kiro","tool-use","eventstream","consistency-check","streaming"],"backgroundTag":"tool-fragment-id-collision","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}