From bdf3886024fe42672cb65a99c12b8a0f2f72707e Mon Sep 17 00:00:00 2001 From: Leon-SK668 <0668001470@xydigit.com> Date: Fri, 17 Jul 2026 08:33:35 +0800 Subject: [PATCH] fix(scripts): preserve emoji in GitHub API errors (#108184) * fix(scripts): keep GitHub errors valid Unicode * style(scripts): format GitHub error boundary test --------- Co-authored-by: Peter Steinberger --- scripts/gh-read.ts | 3 ++- test/scripts/gh-read.test.ts | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/scripts/gh-read.ts b/scripts/gh-read.ts index 6352ebadd38..d5d7db8f2d6 100644 --- a/scripts/gh-read.ts +++ b/scripts/gh-read.ts @@ -4,6 +4,7 @@ import { createPrivateKey, createSign } from "node:crypto"; import { pathToFileURL } from "node:url"; import { readSecretFileSync } from "@openclaw/fs-safe/secret"; import { expectDefined } from "../packages/normalization-core/src/expect.js"; +import { truncateUtf16Safe } from "../packages/normalization-core/src/utf16-slice.js"; import { readBoundedResponseText } from "./lib/bounded-response.ts"; import { parseStrictIntegerOption } from "./lib/dev-tooling-safety.ts"; import { @@ -238,7 +239,7 @@ export async function readBoundedGitHubErrorText( text += decoder.decode(value, { stream: true }); if (text.length > maxChars) { - text = text.slice(0, maxChars); + text = truncateUtf16Safe(text, maxChars); truncated = true; break; } diff --git a/test/scripts/gh-read.test.ts b/test/scripts/gh-read.test.ts index a0857397f46..2194c307314 100644 --- a/test/scripts/gh-read.test.ts +++ b/test/scripts/gh-read.test.ts @@ -173,6 +173,26 @@ describe("gh-read helpers", () => { expect(text.length).toBeLessThan(4200); }); + it.each([ + { + caseName: "drops a split surrogate pair", + responseBody: `abc\u{1f600}tail`, + expectedText: "abc\n[truncated]", + }, + { + caseName: "preserves a complete surrogate pair", + responseBody: `ab\u{1f600}tail`, + expectedText: `ab\u{1f600}\n[truncated]`, + }, + ])( + "keeps GitHub API error truncation UTF-16 safe: $caseName", + async ({ responseBody, expectedText }) => { + const response = new Response(responseBody, { status: 500 }); + + await expect(readBoundedGitHubErrorText(response, 4)).resolves.toBe(expectedText); + }, + ); + it("reads bounded GitHub API JSON responses", async () => { await expect(readBoundedGitHubJson(new Response('{"id":123}'), 1024)).resolves.toEqual({ id: 123,