fix(run_agent): recover first object from concatenated tool_call args (#25333) - #26053
Closed
spranab wants to merge 1 commit into
Closed
fix(run_agent): recover first object from concatenated tool_call args (#25333)#26053spranab wants to merge 1 commit into
spranab wants to merge 1 commit into
Conversation
…NousResearch#25333) `gemini-3-flash-preview` occasionally emits two parallel tool_call argument deltas merged into one streaming buffer with no delimiter, producing inputs like: {"entity": "X", "relationship": "works_at", "target": "Y"}{"entity": "X", ...} The existing repair cascade (strict=False, trailing-comma strip, brace-balance heuristic, control-char escape) does not handle this shape — strict=False fails on the extra data, the brace counts are balanced (so the heuristic is a no-op), and the input falls through to the {} fallback. Both tool calls are dropped. Add a new repair pass between strict=False and the brace-balance heuristic that uses json.JSONDecoder.raw_decode to extract the first complete top-level JSON value. If anything non-whitespace follows that boundary, we log a warning naming NousResearch#25333 and return the first object; at least one of the two parallel tool calls then lands instead of both being dropped. raw_decode respects JSON string semantics, so the boundary detector is not fooled by `}{` characters that appear inside string values (test covers this). This is the parser-side counterpart to the streaming-accumulator fix in NousResearch#24676 — that PR fixes the upstream cause (the accumulator shouldn't merge parallel deltas in the first place); this PR makes the parser graceful when the upstream cause does slip through. Tests: 7 new cases covering two/three concatenated objects, braces- inside-strings (no false positive), trailing-whitespace (no false positive), array-shaped values, and the malformed-first-object fallback. 27 total cases pass. Reported and reproducible from yantrikos/yantrikdb-hermes-plugin#5. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 15, 2026
Collaborator
Author
|
Thanks for the cross-reference — agreed, #26053 is a duplicate of #25347 (same #25346's approach is strictly better for the actual symptom in #25333:
Closing this PR. One small thing from this branch that may be worth pulling forward (against #25346 or its review feedback): the test case Apologies for not checking existing PRs against #25333 before filing — won't repeat the mistake. |
This was referenced Jul 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
gemini-3-flash-previewoccasionally emits two parallel tool-call argument deltas merged into one streaming buffer with no delimiter, producing inputs like:The existing
_repair_tool_call_argumentscascade (strict=False → trailing-comma strip → brace-balance heuristic → control-char escape) doesn't handle this shape:strict=Falsejson.loadsfails on the extra data ("Extra data: line 1 column N"){and 2}), so the brace-balance heuristic is a no-op{}fallback — both tool calls are droppedThis PR adds a repair pass that uses
json.JSONDecoder.raw_decode()to extract the first complete top-level JSON value. If anything non-whitespace follows the parsed boundary, we log a warning naming #25333 and return the first object. At least one of the two parallel tool calls then lands instead of both being dropped.raw_decoderespects JSON string semantics, so the boundary detector is not fooled by}{characters appearing inside string values (testtest_concatenated_objects_with_braces_inside_stringscovers this).Relationship to PR #24676
This is the parser-side counterpart to the streaming-accumulator fix in #24676. That PR fixes the upstream cause (the accumulator shouldn't merge parallel deltas in the first place); this PR makes the parser graceful when the upstream cause does slip through. The two are complementary; both can land independently.
Test Plan
tests/run_agent/test_repair_tool_call_arguments.pycovering:{"snippet": "if (cfg) { run(); }"}) → no false-positive boundarypython -m pytest tests/run_agent/test_repair_tool_call_arguments.py -p no:xdist -o addopts= -vReproducer
The bug surfaced in yantrikos/yantrikdb-hermes-plugin#5, filed as #25333 here. With
model=gemini-3-flash-previewand the message "import the builtin memories into yantrikdb", Hermes logs:The trailing
{"entityis the start of a secondyantrikdb_relatecall. With this patch the first call now lands cleanly; the second is dropped with a clear log line so operators can investigate further. Closing #24676 will additionally fix the upstream merge so neither call is ever dropped.Refs: