decolua/9router · error
Kiro toolUseEvent has an invalid toolUseId
Error message
Kiro toolUseEvent has an invalid toolUseId
What it means
The executor accepts a toolUseId that is either null/undefined (it then synthesizes call_<created>_<n>) or a non-empty trimmed string. A toolUseId that is present but not a usable string — wrong type, empty string, or whitespace-only — is treated as a corrupted fragment and throws, since tool fragments are keyed by id and a bad id would break fragment correlation and deduplication.
Source
Thrown at open-sse/executors/kiro.js:837
state.totalContentLength += content.length;
emitDelta(controller, { reasoning_content: content });
}
} else if (eventType === "codeEvent" && typeof event.payload?.content === "string") {
state.hasCode = true;
state.totalContentLength += event.payload.content.length;
emitDelta(controller, { content: event.payload.content });
} else if (eventType === "toolUseEvent") {
state.sawToolUse = true;
const values = Array.isArray(event.payload) ? event.payload : [event.payload];
if (!values[0]) throw new Error("Kiro toolUseEvent is empty");
for (const value of values) {
const name = typeof value?.name === "string" ? value.name.trim() : "";
if (!name) throw new Error("Kiro toolUseEvent is missing a tool name");
let id;
if (value.toolUseId == null) {
id = `call_${created}_${state.tools.size + 1}`;
} else if (typeof value.toolUseId !== "string" || !value.toolUseId.trim()) {
throw new Error("Kiro toolUseEvent has an invalid toolUseId");
} else {
id = value.toolUseId;
}
let tool = state.tools.get(id);
if (!tool) {
tool = { id, name };
state.tools.set(id, tool);
state.bufferedToolBytes += encoder.encode(id).byteLength + encoder.encode(name).byteLength + 32;
assertToolBufferBound();
} else if (tool.name !== name) {
throw new Error("Kiro tool name changed between fragments");
}
appendToolInput(tool, value.input);
}
} else if (eventType === "messageStopEvent") {
state.explicitStop = true;
const reason = normalizeStopReason(
event.payload?.stopReason ?? event.payload?.stop_reasonView on GitHub (pinned to 90b52e06ff)
Solutions
- Retry the request once — transient corrupted id fragments are usually not reproducible
- Capture the raw EventStream event to confirm the actual toolUseId type/value being received
- If upstream now sends numeric ids, coerce instead of throwing: String(value.toolUseId).trim() with a length check
- Update the executor's id handling if a Kiro API version change altered the id format
Example fix
// before: reject any non-string id
} else if (typeof value.toolUseId !== 'string' || !value.toolUseId.trim()) {
throw new Error('Kiro toolUseEvent has an invalid toolUseId');
}
// after: coerce scalar ids to strings, only reject truly unusable values
} else if (typeof value.toolUseId === 'string' && value.toolUseId.trim()) {
id = value.toolUseId.trim();
} else if (typeof value.toolUseId === 'number' && Number.isFinite(value.toolUseId)) {
id = `call_${value.toolUseId}`;
} else {
throw new Error('Kiro toolUseEvent has an invalid toolUseId');
} Defensive patterns
Strategy: try-catch
Type guard
function hasValidToolUseId(value) {
return value?.toolUseId == null ||
(typeof value.toolUseId === 'string' && value.toolUseId.trim().length > 0);
} Try / catch
try {
return await streamKiro(req);
} catch (e) {
if (/invalid toolUseId/.test(e.message) && attempts < 2) return retry(req);
throw e;
} Prevention
- Retry once — corrupted id fragments are typically transient
- Verify no proxy/middleware between you and Kiro rewrites or type-coerces event payloads
- Check upstream changelogs for toolUseId format changes (e.g. numeric ids) and coerce proactively
- Keep raw EventStream capture enabled at debug level for post-mortem of id corruption
When it happens
Trigger: A toolUseEvent value with value.toolUseId set to a non-string (number, object), an empty string, or whitespace-only string. E.g. upstream sent toolUseId: 0 or toolUseId: "" in a continuation fragment.
Common situations: Upstream serializer type drift (numeric ids instead of strings); Kiro emitting placeholder empty ids during degraded responses; middleware/proxies rewriting event payloads and coercing id types.
Related errors
- Kiro toolUseEvent is empty
- Kiro toolUseEvent is missing a tool name
- Kiro tool name changed between fragments
- Kiro tool_use stop reason did not include a complete tool ca
- AWS EventStream frame is shorter than 16 bytes
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/0dc78cffb117a1bd.
Report an issue: GitHub.