decolua/9router · error
Kiro tool input changed fragment type
Error message
Kiro tool input changed fragment type
What it means
handleStreamingResponse expected an SSE (text/event-stream) response from the upstream provider but got something else (HTML error page, JSON error, login page, empty body). It logs `BLOCKED <status> · provider/model · non-SSE (<content-type>)`, signals streamController.handleError, and returns an HTTP response with status equal to the upstream status (default 502) and message `[<status>]: <shortMsg>`, where shortMsg is the sanitized <title> or the trimmed body text.
Source
Thrown at open-sse/executors/kiro.js:690
state.terminalProvenance = provenance;
state.transportState = extra.transport_state || "corrupt_frame";
const detail = diagnostics({
stop_disposition: extra.stop_disposition || "terminal_incomplete",
...extra
});
options.onTerminalState?.(detail);
controller.enqueue(encodeSSEError(code, message, detail));
};
const assertToolBufferBound = () => {
if (state.bufferedToolBytes <= (options.maxToolBytes || KIRO_REPAIR_BUFFER_MAX_BYTES / 2)) return;
const error = new Error("Kiro buffered tool input exceeded the integrity memory bound");
error.code = "KIRO_BUFFER_EXCEEDED";
throw error;
};
const appendToolInput = (tool, input) => {
if (input === undefined) return;
if (typeof input === "string") {
if (tool.inputKind && tool.inputKind !== "string") throw new Error("Kiro tool input changed fragment type");
tool.inputKind = "string";
tool.inputChunks ||= [];
tool.inputChunks.push(input);
state.bufferedToolBytes += encoder.encode(input).byteLength;
} else if (input && typeof input === "object" && !Array.isArray(input)) {
if (tool.inputKind && tool.inputKind !== "object") throw new Error("Kiro tool input changed fragment type");
tool.inputKind = "object";
state.bufferedToolBytes -= tool.inputBytes || 0;
tool.inputObject = input;
tool.inputBytes = encoder.encode(JSON.stringify(input)).byteLength;
state.bufferedToolBytes += tool.inputBytes;
} else {
throw new Error("Kiro tool input must be a JSON object");
}
assertToolBufferBound();
};
const parsedToolInput = (tool) => {
if (!tool.inputKind) throw new Error("Kiro tool call is missing input");View on GitHub (pinned to 90b52e06ff)
Solutions
- Read shortMsg in the error body — it contains the upstream <title> or body text identifying the real problem.
- If it's a login/401 page, refresh the provider connection's credentials (OAuth re-auth or new API key) in the dashboard.
- If 429/quota, wait or switch to a fallback model/account (combo fallback).
- If Cloudflare/WAF HTML, retry, change IP/proxy, or use a connection proxy configured for that provider.
- Verify the provider's base URL points at its API endpoint, not the website.
Example fix
// before: raw upstream HTML/JSON surfaces opaquely
const status = providerResponse.status || 502;
// after: check credentials proactively so upstream never returns an HTML error page
if (!connection.accessToken) throw new Error('provider credentials missing');
const status = providerResponse.status || 502; Defensive patterns
Strategy: type-guard
Validate before calling
// inspect the structured error the router returns before retrying
const body = await res.json();
const m = /^\[(\d+)\]:\s(.*)$/.exec(body.error?.message || '');
if (m) {
const [_, upstreamStatus, detail] = m;
if (upstreamStatus === '401' || /sign in|login/i.test(detail)) await reauthorizeProvider();
else if (upstreamStatus === '429') await backoff();
} Type guard
function isNonSseUpstreamError(payload) {
return typeof payload?.error?.message === 'string'
&& /^\[\d+\]:/.test(payload.error.message);
} Try / catch
const res = await fetchChat(model, messages);
if (!res.ok || !(res.headers.get('content-type') || '').includes('event-stream')) {
const body = await res.json().catch(() => ({}));
if (isNonSseUpstreamError(body)) {
const [status, detail] = body.error.message.match(/^\[(\d+)\]:\s(.*)$/).slice(1);
// 401/403 → refresh credentials; 429 → backoff/fallback; 5xx → retry/proxy
}
} Prevention
- Keep provider credentials fresh — most non-SSE responses are login/401 HTML pages.
- Configure combo/account fallback so 429/5xx upstreams fail over automatically.
- Point provider base URLs at the API endpoint, never the marketing site.
- Route providers through a connection proxy when Cloudflare/WAF challenges recur.
- Parse the `[status]: detail` message format — the embedded title/body names the true cause.
When it happens
Trigger: An SSE chat completion request reaches an upstream that answers with a non-SSE content type — e.g. 401/403 HTML login page, 429 rate-limit JSON, 502/503 Cloudflare error page, or a captive-portal/CDN block — and the provider response passes through the streaming path.
Common situations: Expired/invalid provider credentials (login HTML returned); rate limiting or quota exhaustion; Cloudflare/WAF challenge pages; wrong base URL pointing at a website instead of the API; region blocks; upstream outage returning an error page.
Related errors
- Kiro tool_use stop reason did not include a complete tool ca
- [antigravity] ${error.message}
- Machine ID is required for Cursor API
- http2 module not available
- Kiro toolUseEvent is empty
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/6dea011184e3e014.
Report an issue: GitHub.