Cut a long Composio result or failure between characters, not through an emoji - #568
Merged
davidmckayv merged 1 commit intoSep 15, 2026
Conversation
… an emoji The Composio transport caps every string it puts in front of a model at MAX_RESULT_CHARS in `cap`, for an action's answer and for a failure sentence alike, and it cut with `slice`, which counts UTF-16 code units. When the cap landed between the halves of a surrogate pair, the text the model read ended on a lone high surrogate: JSON carries it as a bare `\ud83d` and UTF-8 encodes it as U+FFFD, so the model was handed a broken character that was never in what Composio sent. The MCP and built-in transports have stopped one code unit short in this case since CopilotKit#525, through `cutAtCodeUnits`. The Composio transport arrived after that with its own cut and now uses the same helper. Text that fits, and a cut that lands between characters, are unchanged, and so is the note saying the answer was cut. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
kevin9327
requested review from
MikeRyanDev,
davidmckayv,
guidovizoso,
mxmzb and
tylerslaton
as code owners
September 15, 2026 22:03
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
9 tasks
davidmckayv
approved these changes
Sep 15, 2026
davidmckayv
left a comment
Contributor
There was a problem hiding this comment.
Code-verified clean; CI green on this sha.
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.
What this changes
capinserver/src/plugins/composio.tsis the cap every string the Composio transport puts in front of a model goes through: an action's answer (resultOf) and a failure sentence (failure). It cut atMAX_RESULT_CHARSwithslice, which counts UTF-16 code units. When the cut landed between the two halves of an emoji, or any other character outside the Basic Multilingual Plane, the text ended on a lone high surrogate. JSON carries that as a bare\ud83dand UTF-8 as U+FFFD, so the model read a broken character that Composio never sent. Mail bodies, chat messages and documents are exactly the answers long enough to reach the cap and likely to hold emoji.#525 fixed this cut in the MCP transport and both built-in transports with
cutAtCodeUnitsinchannels/text.ts. The Composio transport landed afterwards (#481) with its ownslice, so this is the fourth transport's copy of the same cut. It now calls the same helper: one import and one call.Text that fits, a cut that lands between characters, the
[truncated]note andtruncated: trueare all unchanged. The result is at most one code unit shorter, never longer than the cap.Where it runs
Boundary and audit
Changelog
CHANGELOG.mdunderUnreleased.Unreleasedwas empty, so this entry sits at its top. If another PR lands there first, I'm happy to rebase.Proof
New test in
server/tests/composio-transport.test.ts: "a result or a failure over the cap is cut between characters, not through an emoji". It drives both answers throughcallToolwith an emoji whose high surrogate lands on the cap's last unit. The result goes throughJSON.stringify(data, null, 2), and the test accounts for its 12-unit opening.On
mainwithout the fix, both come back cut through the emoji:With the fix:
bun test tests/composio-transport.test.ts tests/mcp-result.test.ts: 122 pass, 0 fail. This includes the existing boundary test ("a result the exact size of the cap is not cut, and one character more is") and "a result is capped visibly rather than silently", both unchanged.bun run typecheck(app, server, worker): exit 0.bunx biome checkon the changed files: clean.🤖 Generated with Claude Code