Tool result size limit can return output larger than maxBytes for small limits
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 4k
- Forks
- 453
- Avg merge
- 12h 30m
- Merged PRs (30d)
- 46
Description
Tool result size limit can return output larger than maxBytes for small limits
Summary
applyResultSizeLimit() treats maxBytes as disabled only when it is undefined or negative, so 0 and other small non-negative values are valid limits. When the limit is smaller than the fixed truncation suffix, the returned text can still contain the full suffix and exceed the requested cap.
For example, a one-byte text result with maxBytes = 0 returns a 46-byte truncation notice, and metadata.returnedBytes records 46.
Affected revision
Observed on main at 9ad10eb0e1ed75f864ca8cbda7f659f7c3b163e9.
Affected code
src/tool/protocol/result.ts:140-143definesapplyResultSizeLimit(content, maxBytes).src/tool/protocol/result.ts:144-146treats onlyundefinedand negative limits as disabled.src/tool/protocol/result.ts:162-167appends the fixed truncation suffix after calculating a zero budget.
Reproduction
From the repository root:
cat > repro-tool-result-limit.mts <<'EOF'
import { applyResultSizeLimit } from "./src/tool/protocol/result.ts";
const result = applyResultSizeLimit([{ type: "text", text: " " }], 0);
const text = result.content[0]?.type === "text" ? result.content[0].text : "";
const bytes = Buffer.byteLength(text, "utf8");
console.log(JSON.stringify({ bytes, metadata: result.metadata, text }, null, 2));
EOF
pnpm exec tsx repro-tool-result-limit.mts
rm repro-tool-result-limit.mts
Observed output:
{
"bytes": 46,
"metadata": {
"truncated": true,
"originalBytes": 1,
"returnedBytes": 46
},
"text": "\n[Tool output truncated: head and tail shown.]"
}
Expected behavior
When maxBytes is a non-negative limit and the original non-media content is larger than that limit, the returned text and metadata.returnedBytes should not exceed maxBytes.
For tiny limits, returning an empty string or a shortened truncation notice would both preserve the limit. The key invariant is that the visible returned content stays within the requested byte cap.
Actual behavior
The function computes:
const budget = Math.max(0, maxBytes - suffixBytes);
const truncatedText = headTailTruncateUtf8(text, budget) + suffix;
When maxBytes < suffixBytes, budget becomes 0, but the full suffix is still appended. This makes the returned text longer than the requested cap.
Impact
applyResultSizeLimit() is used for bounded tool-result previews. Default limits are larger than this minimized case, but the helper does not preserve its own boundary contract for small configured caps or future callers. The metadata also reports truncated: true while recording a returnedBytes value larger than the requested maximum.
Existing coverage
I could not find an existing issue or pull request covering this small-maxBytes result-size boundary.
Suggested fix
Apply the byte cap to the final returned string, including the suffix. In the maxBytes <= suffixBytes case, either truncate the suffix itself or return an empty preview, then set metadata.returnedBytes from the actual returned string.
Suggested regression tests:
maxBytes = 0maxBytes = suffixBytes - 1- multibyte text around the boundary, to preserve UTF-8 safety
Submitted with Codex.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/tool/protocol/result.ts at applyResultSizeLimit() and inspect how the truncation suffix and returnedBytes are calculated. Search for existing result-size-limit tests, then add coverage for maxBytes values of 0, below the suffix size, and multibyte text. Done means returned content and metadata.returnedBytes never exceed a non-negative maxBytes limit while preserving UTF-8 safety.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100