From dfeb15261db3f8e8a5867ad0966faa19313fe80f Mon Sep 17 00:00:00 2001 From: chengzhichao-xydt Date: Sun, 12 Jul 2026 08:08:56 +0800 Subject: [PATCH] fix(agents): keep tool_search_code stderr tail UTF-16 safe (#104767) * fix(agents): keep tool_search_code stderr tail UTF-16 safe * test(agents): add real child-exit UTF-16 safety proof * test(agents): focus stderr UTF-16 regression --------- Co-authored-by: chengzhichao-xydt Co-authored-by: Peter Steinberger --- src/agents/tool-search.stream-errors.test.ts | 43 ++++++++++++++++++++ src/agents/tool-search.ts | 4 +- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/agents/tool-search.stream-errors.test.ts b/src/agents/tool-search.stream-errors.test.ts index 153fb32da394..8abe636bdffe 100644 --- a/src/agents/tool-search.stream-errors.test.ts +++ b/src/agents/tool-search.stream-errors.test.ts @@ -87,4 +87,47 @@ describe("tool-search code-mode stream errors", () => { }); expect(spawnedChild?.kill).toHaveBeenCalledOnce(); }); + + it("keeps stderr tail in exit error messages valid at UTF-16 boundaries", async () => { + toolSearch.testing.setToolSearchCodeModeSupportedForTest(true); + toolSearch.testing.setToolSearchMinCodeTimeoutMsForTest(1000); + + spawnMock.mockImplementationOnce( + (_command: string, _args: readonly string[], _options: SpawnOptions): ChildProcess => { + const { child, stderr } = createMockSpawnChild(); + process.nextTick(() => { + stderr?.emit("data", `${"a".repeat(500)}😀${"a".repeat(499)}`); + process.nextTick(() => { + child.emit("exit", 1, null); + }); + }); + return child as unknown as ChildProcess; + }, + ); + + const runtime = new toolSearch.ToolSearchRuntime( + {}, + toolSearch.testing.resolveToolSearchConfig({}), + ); + + let caught: Error | undefined; + try { + await toolSearch.testing.runCodeModeChild({ + code: "return 1;", + config: toolSearch.testing.resolveToolSearchConfig({}), + logs: [], + parentToolCallId: "call-stderr-utf16", + runtime, + }); + } catch (error) { + caught = error instanceof Error ? error : new Error(String(error)); + } + + expect(caught).toBeDefined(); + expect(caught?.message).toMatch(/tool_search_code child exited with 1/); + expect(caught?.message).not.toMatch( + /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(? { const suffix = stderrTail.trim(); - const detail = suffix ? `: ${suffix.slice(-500)}` : ""; + const detail = suffix ? `: ${sliceUtf16Safe(suffix, -500)}` : ""; settle(() => reject( new Error(