fix: guard against binary output context exhaustion - #18
Merged
Conversation
Salil Das (sadlilas)
marked this pull request as draft
August 19, 2026 02:08
Binary payloads returned by bash commands cost tens of thousands of tokens of noise the model cannot use. Per-call truncation bounds a single call but not a session -- a few dozen such calls exhaust even a 1M-token context -- so binary streams are now withheld entirely and replaced with a short, actionable placeholder. Detection runs on the RAW BYTES from the subprocess, in two stages: 1. If the bytes decode as strict UTF-8, the stream is text. This covers ASCII, UTF-8, and the NUL-delimiter idioms (`find -print0`, `grep -z`, `xargs -0`), since NUL is valid UTF-8. NUL is deliberately not used as a binary marker the way tool-web does: in shell output it is a legitimate delimiter, and keying off it would break the standard safe-filename idiom. 2. Otherwise the stream is either binary or text in a legacy 8-bit encoding. These separate cleanly on the proportion of C0/C1 control bytes, which are pervasive in binary and absent from text. Detecting on the decoded string's U+FFFD ratio was implemented first, then measured and rejected: it is inverted on both sides. Real executables carry large ASCII string tables and NUL padding that decode cleanly (/bin/cat 3.6%, python3 2.3%) and would slip past, while text in legacy encodings is high-bit on nearly every character (cp1251 Russian 80.9%, shift_jis Japanese 64.8%) and would be destroyed. On the control-byte measure every real binary tested scored >= 6.0% and every text sample scored 0.0%. Also fixes reported output sizes: stdout_bytes/stderr_bytes now come from the raw subprocess bytes rather than a re-encode of the decoded string, which inflated them (U+FFFD re-encodes to 3 bytes for what was a 1-byte input). Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Salil Das (sadlilas)
force-pushed
the
fix/binary-output-context-exhaustion
branch
from
August 19, 2026 02:15
185e2fd to
46e2e75
Compare
Salil Das (sadlilas)
marked this pull request as ready for review
August 19, 2026 02:16
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
When a bash command emits binary data —
caton a binary file,head -c N /dev/urandom, a stray blob on stderr — that data is decoded witherrors="replace"and flows straight into the model's context as noise it cannot use._truncate_output()bounds a single call at ~100KB, but it does not bound a session. A 500KB binary payload still costs tens of thousands of tokens after truncation, so a few dozen such calls exhaust even a 1M-token context.This PR withholds binary streams entirely rather than truncating them, and replaces each with a short, actionable placeholder.
The Fix
_guard_binary_output()runs per-stream, before truncation, on the raw bytes from the subprocess:find -print0,grep -z,xargs -0) — NUL is valid UTF-8.The placeholder carries the byte count, the control-byte percentage, and a file-redirect workflow (
command > out.bin, thenfile out.binorxxd out.bin | head). Output gains abinary_output_withheld: Trueflag when either stream is withheld.Also fixed:
stdout_bytes/stderr_bytesnow report the true subprocess byte counts. They were previously derived by re-encoding the decoded string, which inflated them — U+FFFD re-encodes to 3 bytes for what was a 1-byte input.Design Notes
Why not key off NUL bytes? It's the obvious binary marker and what web-content tools use, but in shell output NUL is a legitimate delimiter.
find -print0,grep -z, andxargs -0emit it on purpose. A NUL-keyed guard would break the standard safe-filename idiom; test coverage pins this.Why detect on raw bytes, not the decoded string's U+FFFD ratio? The U+FFFD approach was implemented first, then measured and rejected — it is inverted on both sides:
/bin/cat(real binary)python3(real binary)Real executables carry large ASCII string tables and NUL padding that decode cleanly, so they would slip past a U+FFFD threshold. Legitimate text in legacy encodings is high-bit on nearly every character, so it would be destroyed. On the control-byte measure the two populations separate cleanly: every real binary tested scored ≥ 6.0%, every text sample scored 0.0%.
No configuration knob is exposed. The threshold sits in the middle of a wide empirical gap, and a knob would be a speculative surface with no caller asking for it.
Verification
Full suite: 141 passed, 12 skipped — no regressions.
New coverage (
tests/test_binary_output_guard.py): 27 testsfind -print0/grep -z/xargs -0pass through untouchedEnd-to-end against real
BashTool.execute():head -c 500000 /dev/urandom→ withheld; ~906KB of decoded noise reduced to a ~217-char placeholderfind . -name '*.txt' -print0→ passes through, NUL delimiters intactfind . -name '*.txt' -print0 | xargs -0 -n1 basename→ full idiom worksprintf 'café 日本語 🚀 ok'→ untouchedCode quality:
ruff checkclean on all changed files. (ruff formatreports one pre-existing deviation in__init__.pyunrelated to this change; left untouched to keep the diff scoped.)