fix(routers): a tool call with no arguments needs a clean end-of-stream

↗ view on GitHub · Amal · 2026-08-18 · dd7cd96d

Second pass of the adversarial review on this stack: the truncated-
arguments guard added earlier in the branch left one hole open, and it
is the hole that runs a side-effecting tool.

WHY THIS MATTERS
"Arguments present but unparseable" already fails the stream. But the
hole was the case with NO argument bytes at all. The carve-out read
`if (partial.arguments.trim())` - so an empty string skipped every check
and the call executed with `input: {}`. A stream that died right after
the `function.name` delta and before the first `{` therefore ran
delete_document (or any tool whose no-argument form is meaningful) with
empty input, which is exactly the failure mode the guard was written to
prevent.

WHY THE EMPTY STRING CANNOT DECIDE THIS
A parameter-less tool also streams "" - the two cases are byte-identical
in the accumulated arguments. The only thing that tells them apart is
whether the upstream ever said why it stopped. So the carve-out is
re-keyed on a termination signal instead of on the payload:
`endedCleanly` is set when the SSE `[DONE]` sentinel arrives or when a
choice carries `finish_reason: "tool_calls"`. Empty arguments plus a
clean end still mean {}; empty arguments after a silent socket close now
throw the same descriptive error as malformed JSON.

Handling `[DONE]` as a signal rather than a skip also tightened the line
parser: the sentinel and an empty `data:` line are now separate branches
instead of one combined early return.

Tests: openrouter.test.ts gains the died-before-arguments case (no
[DONE], no finish_reason - must reject and never call runTools) and a
parameter-less-tool case that pins {} still executes when the stream
terminated cleanly. Pre-fix the first test does not merely fail its
assertion - it proves the bug, erroring with "results is not iterable"
because runTools WAS invoked for the aborted call.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Repository open-legal-products/mike
Author Amal <mamalanand3@gmail.com>
Authored
Committed
Parents e414b530
Stats 2 files changed , +121 , -5
Part of Harden model-router selection and failure handling

Capture this commit into my fork

Download a Markdown prompt that tells Claude how to port this exact commit into your working tree. Run it via claude -p < capture-commit-dd7cd96d.md from inside the repo you want the change in.

⬇ Download capture-commit-dd7cd96d.md