From f786efddcd88445d6678124341a1c939d5a41bd8 Mon Sep 17 00:00:00 2001
From: lzyyzznl
Date: Thu, 9 Jul 2026 20:05:51 +0800
Subject: [PATCH] fix(cron): keep bounded diagnostics UTF-16 safe (#102624)
* fix(cron): use truncateUtf16Safe for diagnostic entry and summary truncation
* test(cron): cover UTF-16 diagnostic boundaries
---------
Co-authored-by: Peter Steinberger
---
src/cron/run-diagnostics.test.ts | 23 +++++++++++++++++++++++
src/cron/run-diagnostics.ts | 5 +++--
2 files changed, 26 insertions(+), 2 deletions(-)
diff --git a/src/cron/run-diagnostics.test.ts b/src/cron/run-diagnostics.test.ts
index 39f3f8a89df..3e28ac3bd0d 100644
--- a/src/cron/run-diagnostics.test.ts
+++ b/src/cron/run-diagnostics.test.ts
@@ -31,6 +31,29 @@ describe("cron run diagnostics", () => {
expect(diagnostics?.summary).toHaveLength(2_000);
});
+ it("keeps bounded diagnostic text valid at UTF-16 boundaries", () => {
+ const diagnostics = normalizeCronRunDiagnostics({
+ summary: `${"s".repeat(1_998)}😀tail`,
+ entries: [
+ {
+ ts: 1,
+ source: "exec",
+ severity: "error",
+ message: `${"m".repeat(998)}😀tail`,
+ },
+ ],
+ });
+
+ expect(diagnostics?.summary).toBe(`${"s".repeat(1_998)}…`);
+ expect(diagnostics?.entries[0]).toEqual({
+ ts: 1,
+ source: "exec",
+ severity: "error",
+ message: `${"m".repeat(998)}…`,
+ truncated: true,
+ });
+ });
+
it("preserves later terminal diagnostics when capping entries", () => {
const diagnostics = normalizeCronRunDiagnostics({
entries: [
diff --git a/src/cron/run-diagnostics.ts b/src/cron/run-diagnostics.ts
index d94829d606a..5f72a1d039f 100644
--- a/src/cron/run-diagnostics.ts
+++ b/src/cron/run-diagnostics.ts
@@ -1,5 +1,6 @@
/** Builds bounded, redacted diagnostics for cron run logs and UI surfaces. */
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
+import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
import { isToolAllowedByPolicyName } from "../agents/tool-policy-match.js";
import { normalizeToolName as normalizePolicyToolName } from "../agents/tool-policy.js";
import { getReplyPayloadMetadata } from "../auto-reply/reply-payload.js";
@@ -101,7 +102,7 @@ function normalizeDiagnosticMessage(value: unknown): { message?: string; truncat
if (redacted.length <= MAX_ENTRY_CHARS) {
return { message: redacted };
}
- return { message: `${redacted.slice(0, MAX_ENTRY_CHARS - 1)}…`, truncated: true };
+ return { message: `${truncateUtf16Safe(redacted, MAX_ENTRY_CHARS - 1)}…`, truncated: true };
}
function trimSummary(value: string | undefined): string | undefined {
@@ -112,7 +113,7 @@ function trimSummary(value: string | undefined): string | undefined {
if (normalized.length <= MAX_SUMMARY_CHARS) {
return normalized;
}
- return `${normalized.slice(0, MAX_SUMMARY_CHARS - 1)}…`;
+ return `${truncateUtf16Safe(normalized, MAX_SUMMARY_CHARS - 1)}…`;
}
/** Returns the operator-facing summary for persisted cron diagnostics. */