Skip to content

fix(sse): scan tool markup in web executors in linear time - #15066

Merged
diegosouzapw merged 2 commits into
diegosouzapw:release/v3.8.51from
HouMinXi:fix/web-tool-markup-linear-scan
Sep 29, 2026
Merged

diegosouzapw merged 2 commits into
diegosouzapw:release/v3.8.51from
HouMinXi:fix/web-tool-markup-linear-scan

Conversation

@HouMinXi

@HouMinXi HouMinXi commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ base-red inherited: #15032

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

  • None.

Validation

  • Change type: other (availability hardening)
  • Focused tests and category gates from the golden path: the tests listed below, the 37 existing test files that use these modules, npm run typecheck:core
  • npm run lint on the changed files (Prettier and ESLint clean)
  • Reconciled with the current active release base (release/v3.8.51 at b27c254430); focused checks rerun afterward
  • Production-code changes include a new or updated automated test in this PR

Each 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.ts

Coverage 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.ts fails on the current tip with and without this change (a worker cannot resolve src/lib/dataPaths in that runtime). The same \s*([\s\S]*?)\s* shape remains in open-sse/executors/devin-agentic/toolParser.ts and promptql/eventTree.ts; both only see model output and are left for a follow-up that can reuse findTagBlocks.

Maintainer rework (merge-batch 2026-09-28 (release drain))

  • TOOL_CALL_OPEN_RE still used [^>]* for the attribute run, so a run of unterminated <tool_call tags 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 to web-tool-markup-linear-scan.test.ts (red on the previous head at ~17.7 s, green now at <100 ms).
  • The new test used (c: any); no-explicit-any is an error under tests/, so it is typed now.
  • Validated: the new test plus web-tools-translation*.test.ts, web-tools-contract-7679, issue-14208-deepseek-web-dsml-invoke-format, grok-web-executor-split (61/61).

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
diegosouzapw merged commit 2837525 into diegosouzapw:release/v3.8.51 Sep 29, 2026
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!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants