fix(sse): scan tool markup in web executors in linear time - #15066
Merged
diegosouzapw merged 2 commits intoSep 29, 2026
Merged
diegosouzapw merged 2 commits into
diegosouzapw merged 2 commits into
Conversation
The Grok Web executor strips injected reminder blocks from every message before it does anything else, with a regex whose two whitespace classes overlap around a newline. A message made of "---" and a long run of newlines makes that regex take time quadratic in the run: 16,000 newlines cost 0.27 s and the cost grows fourfold each time the length doubles, so a body of a few hundred kilobytes, well under the 10 MB request limit, blocks the event loop for minutes and stalls every other request. The reminder scan also restarts at each unclosed opening tag, and the tool-call parsers (`<tool_call>` in Grok Web, `<tool>` and `<tool_call ...>` in the shared web-tools translator used by the other cookie-session executors) have the same overlapping-whitespace shape on text a model writes, and time grew much faster than quadratic there (2,000 spaces took 2.5 s). Find the blocks with one forward scan instead: locate an opening tag, look for the closing tag after it, continue after it, and stop when an opening tag has no closing tag left. The result for well-formed input is the same as before, which the tests check against the old regexes, including the separator line that is removed together with a reminder block. Signed-off-by: Minxi Hou <houminxi@gmail.com>
…d tags stay linear An unterminated '<tool_call ' made the attribute run [^>]* scan to the end of the text from every such tag, so a run of them was still quadratic (20k tags: ~14 s). Stopping the run at '<' keeps each attempt bounded by the next tag. Adds that hostile input to the linear-scan test and types the tool-call map without 'any' (no-explicit-any is an error under tests/).
diegosouzapw
merged commit Sep 29, 2026
2837525
into
diegosouzapw:release/v3.8.51
11 of 16 checks passed
diegosouzapw
pushed a commit
that referenced
this pull request
Sep 29, 2026
Replaces the cubic `<tool>\s*([\s\S]*?)\s*</tool>`, `<summary>` and `<final_response>` regexes with the shared linear findTagBlocks scanner (sibling of #15066). Evidence (tip ed664fc merged into the head): new test hangs to the 400 s timeout on the tip and passes 6/6 in ~1 s with the fix; 18 Devin/PromptQL neighbor test files green; typecheck:core, check:open-sse-typecheck, eslint, prettier and check-file-size clean. Thanks @HouMinXi!
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
The Grok Web executor strips injected reminder blocks from every message before it does anything else, with a regex whose two whitespace classes overlap around a newline. A message made of
---and a long run of newlines makes that regex take time quadratic in the run: 16,000 newlines cost 0.27 s and the cost grows fourfold each time the length doubles, so a body of a few hundred kilobytes, well under the 10 MB request limit, blocks the event loop for minutes and stalls every other request. The reminder scan also restarts at each unclosed opening tag, and the tool-call parsers (<tool_call>in Grok Web,<tool>and<tool_call ...>in the shared web-tools translator used by the other cookie-session executors) have the same overlapping-whitespace shape on text a model writes, and time grew much faster than quadratic there (2,000 spaces took 2.5 s).Find the blocks with one forward scan instead (
findTagBlocks): locate an opening tag, look for the closing tag after it, continue after it, and stop when an opening tag has no closing tag left. The result for well-formed input is the same as before, which the tests check against the old regexes, including the separator line that is removed together with a reminder block.Related Issues
Validation
npm run typecheck:corenpm run linton the changed files (Prettier and ESLint clean)release/v3.8.51atb27c254430); focused checks rerun afterwardEach new timing test was run against the old regexes (it fails) and against the change (it passes).
Tests Added Or Updated
tests/unit/web-tool-markup-linear-scan.test.tsCoverage Notes
The listed test exercises every changed production path: the equivalence with the old reminder-stripping regexes, the Grok tool-call parser, both web-tools block shapes, and hostile inputs (long whitespace runs, many unclosed tags) for each scanner.
Reviewer Notes
tests/unit/grok-web-stream-error-boundary.test.tsfails on the current tip with and without this change (a worker cannot resolvesrc/lib/dataPathsin that runtime). The same\s*([\s\S]*?)\s*shape remains inopen-sse/executors/devin-agentic/toolParser.tsandpromptql/eventTree.ts; both only see model output and are left for a follow-up that can reusefindTagBlocks.Maintainer rework (merge-batch 2026-09-28 (release drain))
TOOL_CALL_OPEN_REstill used[^>]*for the attribute run, so a run of unterminated<tool_calltags scanned to the end of the text from every tag (20k tags: ~14 s). The run now stops at<as well ([^<>]*); the hostile input was added toweb-tool-markup-linear-scan.test.ts(red on the previous head at ~17.7 s, green now at <100 ms).(c: any);no-explicit-anyis an error undertests/, so it is typed now.web-tools-translation*.test.ts,web-tools-contract-7679,issue-14208-deepseek-web-dsml-invoke-format,grok-web-executor-split(61/61).