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_reason

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Retry the request once — transient corrupted id fragments are usually not reproducible
  2. Capture the raw EventStream event to confirm the actual toolUseId type/value being received
  3. If upstream now sends numeric ids, coerce instead of throwing: String(value.toolUseId).trim() with a length check
  4. 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

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


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/0dc78cffb117a1bd. Report an issue: GitHub.