Skip to content

fix(kiro): retry once when a malformed tool_call wrapper precedes any output - #16495

Merged
diegosouzapw merged 4 commits into
release/v3.8.52from
fix/port-pr-2688-kiro-tool-call-wrapper-repair
Oct 15, 2026
Merged

diegosouzapw merged 4 commits into
release/v3.8.52from
fix/port-pr-2688-kiro-tool-call-wrapper-repair

Conversation

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Summary

  • When a streaming Kiro response emits a malformed tool_call wrapper (missing input.name / input.arguments) before any visible output, OmniRoute now suppresses it and re-sends the request once with a corrective instruction, instead of ending the agent turn with invalid_kiro_tool_call.
  • Gated by a new runtime feature flag KIRO_TOOL_CALL_REPAIR_ENABLED (on by default, toggleable via env or the dashboard/settings override like every other flag).

Attribution

Thanks to @SemonCat for the original implementation.

Changes

  • open-sse/executors/kiro/toolCallRepair.ts (new leaf): in-stream repair wrapper, buildKiroToolCallRepairPayload (appends the instruction to conversationState.currentMessage.userInputMessage.content, validation reason capped at 300 chars, tool arguments never echoed), meaningful-chunk detection (role-only start chunks do not count as output), flag reader.
  • open-sse/executors/kiro.ts: the endpoint loop (branded gateway → CodeWhisperer fallback) is now a local send() reused by the retry; transformEventStreamToSSE accepts onInvalidToolCall (suppress the error frame) and responseId (the retry keeps the first attempt's chunk id); streaming responses are wrapped when the flag is on.
  • open-sse/utils/error.ts + fixture: kiro_tool_call_repair_retry_failed added to the public error identifier vocabulary.
  • featureFlagDefinitions.ts, docs/reference/FEATURE_FLAGS.md (+ .i18n-state.json source hash), flag-count tests 85 → 86.
  • config/quality/file-size-baseline.json: error.ts 1217 → 1218 (one vocabulary line, note added). eslint-suppressions.json: kiro.ts no-unused-vars 3 → 2 (the refactor removed one pre-existing violation).

Design notes (deviations from a header-holding gate):

  • The repair runs inside the response stream, not by holding execute() until the first output. Kiro hands the stream off on its first role-only chunk (fix(resilience): content-stall watchdog aborts live streams during reasoning/thinking phases (signature, encrypted & redacted deltas not counted as output) #15260, readiness/content-stall watchdogs depend on it), so holding headers would regress that contract and TTFT. Consequence: headers are already sent when the retry happens, so a non-OK retry (e.g. 429) ends the stream with kiro_tool_call_repair_retry_failed (HTTP <status> in the message, upstream body not echoed) instead of reaching account fallback. That is the same outcome as today (turn fails), never worse.
  • No buffering, so no buffer cap / TTFT timeout is needed; client cancel aborts the retry fetch and cancels the upstream reader.
  • A malformed wrapper after visible text or reasoning is not retried (cannot be replayed invisibly) and surfaces as invalid_kiro_tool_call as before. With inline <thinking> enabled this limits the repair to turns whose first output is the tool call.
  • Non-streaming requests are unchanged.

Test plan

  • node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test tests/unit/kiro-tool-call-repair.test.ts (isolated DATA_DIR, env -u OMNIROUTE_API_KEY)
    • before the kiro.ts wiring: ℹ tests 10 ℹ pass 5 ℹ fail 5 (flag missing; malformed→valid did 1 fetch instead of 2; retry-failed / non-OK retry / cancel cases failed)
    • after: ℹ tests 10 ℹ pass 10 ℹ fail 0 — covers flag on (default) and flag off (KIRO_TOOL_CALL_REPAIR_ENABLED=false → single attempt, invalid_kiro_tool_call), malformed→valid, malformed→malformed, malformed→429, text-then-malformed (no retry), early text release before upstream completes, client cancel, non-stream, payload builder.
  • All 35 test files referencing Kiro executor / feature-flag registry / public error vocabulary: ℹ tests 312 ℹ pass 311 ℹ fail 1 — the one failure (server-owned-tool-loop-flag.test.ts › pt-BR label of SERVER_OWNED_TOOL_LOOP_ENABLED) reproduces with the base version of that test file and is unrelated (base-red).
  • npm run typecheck:core → exit 0
  • npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files> → 0 errors; npx prettier --check <changed files> → clean
  • node scripts/check/check-file-size.mjs --base-ref origin/release/v3.8.52 → OK
  • npm run check:docs-all → exit 0
  • node scripts/check/check-pr-test-policy.mjs → PASS; GITHUB_BASE_REF=release/v3.8.52 npm run check:test-masking → OK (no weakening)

⚠️ base-red inherited: #15306 — unit/vitest/integration/package-artifact gates on release/v3.8.52

diegosouzapw and others added 2 commits October 13, 2026 09:56
… output

Kiro sometimes emits the MCP wrapper tool `tool_call` without input.name or
input.arguments, which ended the agent turn with invalid_kiro_tool_call.

Streaming responses now pass through an in-stream repair: chunks are forwarded
as they arrive (the role-only start chunk still hands the stream off at once,
#15260), and a malformed wrapper seen before any text, reasoning or tool output
is suppressed, the upstream is cancelled and the request is re-sent once with a
corrective instruction appended to the current user message (validation reason
capped at 300 chars, tool arguments never echoed). The retry reuses the
branded-gateway -> CodeWhisperer endpoint fallback. A failed retry (second
malformed wrapper, non-OK status or network error) ends the stream with
kiro_tool_call_repair_retry_failed. Non-streaming requests and wrappers that
follow visible output keep the previous behavior.

Gated by KIRO_TOOL_CALL_REPAIR_ENABLED (runtime flag, on by default).

Co-authored-by: Edison42 <gn00742754@gmail.com>
Inspired-by: decolua/9router#2688
@github-actions

Copy link
Copy Markdown
Contributor

CI Coverage Report

  • Coverage job: skipped
  • PR test policy: success

Coverage artifact was not available for this run.

…o repair identifier

Condense a two-line comment so the new SAFE_PUBLIC_ERROR_IDENTIFIERS entry does
not grow the frozen file, and drop the now-unneeded 1217->1218 rebaseline.
@diegosouzapw
diegosouzapw merged commit 48113e2 into release/v3.8.52 Oct 15, 2026
38 of 51 checks passed
@diegosouzapw
diegosouzapw deleted the fix/port-pr-2688-kiro-tool-call-wrapper-repair branch October 18, 2026 23:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant