fix(context): cap tokenizer input to prevent quadratic hang - #358
Open
harsh20048 wants to merge 1 commit into
Open
fix(context): cap tokenizer input to prevent quadratic hang#358harsh20048 wants to merge 1 commit into
harsh20048 wants to merge 1 commit into
Conversation
estimateTokens() passes unbounded text to the js-tiktoken BPE encoder. BPE cost is superlinear in the length of an unbroken run of a single character, so pathological input stalls the event loop indefinitely. Measured on Node 22 (cl100k_base), same length, different content: chars repeated "x" prose 5,000 1,218ms 1ms 20,000 18,385ms 3ms 50,000 113,194ms 6ms Real text is unaffected; a long unbroken run is not. This is reachable in production: estimateTurnTokens() tokenises tc.result before truncateToolResult() is applied, and the exec tool returns stdout uncapped, so a base64 blob, minified asset or separator bar in tool output can freeze the agent loop. It also hangs the test suite. context-hardening.test.ts builds a 500,000-char string, which extrapolates to hours; vitest's 30s testTimeout cannot fire because the spin is synchronous and blocks the event loop. `vitest run` therefore never terminates. Fix: sample a 2,000-char prefix and scale linearly, keeping the existing Math.max() floor against the character heuristic. Worst case drops from unbounded to ~187ms; measured error on realistic text is 0.2%. src/__tests__/context-hardening.test.ts: hangs indefinitely -> 29 passed in 1.14s. Full suite: 64 files / 1643 tests pass, and now completes.
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.
Problem
estimateTokens()insrc/agent/context.tspasses unbounded text to the js-tiktoken BPE encoder. BPE cost is superlinear in the length of an unbroken run of a single character, so pathological input stalls the event loop indefinitely.Same length, different content (
cl100k_base):"x"Normal text is completely unaffected — this only bites on long unbroken runs.
It hangs the test suite
src/__tests__/context-hardening.test.ts:166builds a 500,000-char string, which extrapolates to hours.vitest's 30stestTimeoutcannot fire, because the spin is synchronous and blocks the event loop.vitest runtherefore never terminates — one worker sits at ~100% CPU indefinitely.It is reachable in production
estimateTurnTokens()tokenisestc.resultbeforetruncateToolResult()is applied, and theexectool returns stdout uncapped (src/agent/tools.ts:143). A base64 blob, minified asset, or long separator bar in tool output can freeze the agent loop.Fix
Sample a 2,000-char prefix and scale linearly, keeping the existing
Math.max()floor against the character heuristic.Verification
src/__tests__/context-hardening.test.ts: hangs indefinitely → 29 passed in 1.14sReproduction:
🤖 Generated with Claude Code