fix(context_compressor): keep tool-call arguments JSON valid when shrinking#12259
Merged
Conversation
…inking
Pass 3 of `_prune_old_tool_results` previously shrunk long `function.arguments`
blobs by slicing the raw JSON string at byte 200 and appending the literal
text `...[truncated]`. That routinely produced payloads like::
{"path": "/foo.md", "content": "# Long markdown
...[truncated]
— an unterminated string with no closing brace. Strict providers (observed
on MiniMax) reject this as `invalid function arguments json string` with a
non-retryable 400. Because the broken call survives in the session history,
every subsequent turn re-sends the same malformed payload and gets the same
400, locking the session into a re-send loop until the call falls out of
the window.
Fix: parse the arguments first, shrink long string leaves inside the parsed
structure, and re-serialise. Non-string values (paths, ints, booleans, lists)
pass through intact. Arguments that are not valid JSON to begin with (rare,
some backends use non-JSON tool args) are returned unchanged rather than
replaced with something neither we nor the provider can parse.
Observed in the wild: a `write_file` with ~800 chars of markdown `content`
triggered this on a real session against MiniMax-M2.7; every turn after
compression got rejected until the session was manually reset.
Tests:
- 7 direct tests of `_truncate_tool_call_args_json` covering valid-JSON
output, non-JSON pass-through, nested structures, non-string leaves,
scalar JSON, and Unicode preservation
- 1 end-to-end test through `_prune_old_tool_results` Pass 3 that
reproduces the exact failure payload shape from the incident
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 18, 2026
1 task
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
Context compression Pass 3 now produces valid JSON when shrinking long tool-call arguments, ending the MiniMax HTTP 400 (code 2013) "invalid function arguments json string" loop that poisoned sessions on every subsequent turn.
Root cause:
_prune_old_tool_resultsPass 3 sliced rawfunction.argumentsat byte 200 and appended...[truncated], producing unterminated JSON. Strict providers (MiniMax, Anthropic via LiteLLM) rejected it non-retryably; the broken call stayed in session history, so every turn re-sent the same malformed payload → stuck session until/new.Changes
agent/context_compressor.py: add_truncate_tool_call_args_json()— parses args, recursively shrinks long string leaves inside the parsed structure, re-serializes. Non-string leaves (paths, ints, lists) pass through. Non-JSON args returned unchanged.tests/agent/test_context_compressor.py: 8 new tests (7 direct + 1 E2E Pass 3 reproducing the exact failure payload from the incident).scripts/release.py: AUTHOR_MAP entry for @honghua.Validation
write_filewith 671-char args through Pass 3Unterminated string at column 71\uXXXXensure_ascii=False)tests/agent/test_context_compressor.pyE2E harness confirmed the pre-fix output fails
json.loads()withUnterminated string starting at: line 1 column 71— exact match for MiniMax's code=2013 rejection observed in the wild.Issues closed
Credits
All three PRs independently diagnosed the same bug on Apr 17 within 8 hours of each other. Going with honghua's implementation because it preserves arg structure (path/ints/lists stay intact) so the model retains more tool-call context after compression.