From 90c06c04c8f62a85b4c011de4729657cac0bdb52 Mon Sep 17 00:00:00 2001 From: xinmotlanthua Date: Wed, 15 Apr 2026 10:47:21 +0700 Subject: [PATCH] fix: guard against undefined event.content in cron agentTurn payload (#66302) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: remove documentation fences from HEARTBEAT.md template The HEARTBEAT.md template wrapped its content in markdown code fences and a doc heading for display purposes. Since loadTemplate() only strips YAML front matter, these artifacts leaked into generated workspace files, causing isHeartbeatContentEffectivelyEmpty() to consider them non-empty and triggering unnecessary API calls. Remove the markdown fences and doc heading so the template produces clean content after front-matter stripping. Closes #66284 * fix: guard against undefined event.content in cron agentTurn payload When a cron job fires with agentTurn payload, event.content is undefined. parseFaceTags(undefined) returned undefined, which propagated to userContent.startsWith("/") causing a TypeError crash. - Fix parseFaceTags and filterInternalMarkers to return "" for falsy input instead of returning the falsy value itself - Add null coalescing fallback at the gateway call site - Add unit tests for undefined/null/empty string inputs Closes #66283 * fix: address review — remove redundant guards, casts, and unrelated HEARTBEAT.md change * fix: guard against undefined event.content in cron agentTurn payload (#66302) (thanks @xinmotlanthua) --------- Co-authored-by: khanhkhanhlele Co-authored-by: sliverp <870080352@qq.com> --- CHANGELOG.md | 1 + extensions/qqbot/src/utils/text-parsing.test.ts | 12 ++++++++++++ extensions/qqbot/src/utils/text-parsing.ts | 8 ++++---- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edad4daaf20..6194a9709b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ Docs: https://docs.openclaw.ai - fix(bluebubbles): replay missed webhook messages after gateway restart via a persistent per-account cursor and `/api/v1/message/query?after=` pass, so messages delivered while the gateway was down no longer disappear. Uses the existing `processMessage` path and is deduped by #66816's inbound GUID cache. (#66857, #66721) Thanks @omarshahine. - Telegram/native commands: keep Telegram command-sync cache process-local so gateway restarts re-register the menu instead of trusting stale on-disk sync state after Telegram cleared commands out-of-band. (#66730) Thanks @nightq. - Audio/self-hosted STT: restore `models.providers.*.request.allowPrivateNetwork` for audio transcription so private or LAN speech-to-text endpoints stop tripping SSRF blocks after the v2026.4.14 regression. (#66692) Thanks @jhsmith409. +- QQBot/cron: guard against undefined `event.content` in `parseFaceTags` and `filterInternalMarkers` so cron-triggered agent turns with no content payload no longer crash with `TypeError: Cannot read properties of undefined (reading 'startsWith')`. (#66302) Thanks @xinmotlanthua. ## 2026.4.14 diff --git a/extensions/qqbot/src/utils/text-parsing.test.ts b/extensions/qqbot/src/utils/text-parsing.test.ts index 451ca2e3111..a48a540c99d 100644 --- a/extensions/qqbot/src/utils/text-parsing.test.ts +++ b/extensions/qqbot/src/utils/text-parsing.test.ts @@ -2,6 +2,18 @@ import { describe, expect, it, vi } from "vitest"; import { parseFaceTags } from "./text-parsing.js"; describe("parseFaceTags", () => { + it("returns empty string when input is undefined", () => { + expect(parseFaceTags(undefined)).toBe(""); + }); + + it("returns empty string when input is null", () => { + expect(parseFaceTags(null)).toBe(""); + }); + + it("returns empty string when input is empty string", () => { + expect(parseFaceTags("")).toBe(""); + }); + it("skips oversized base64 ext payloads before decoding", () => { const oversizedBase64 = "A".repeat(100_000); const tag = ``; diff --git a/extensions/qqbot/src/utils/text-parsing.ts b/extensions/qqbot/src/utils/text-parsing.ts index 7722bfd04c6..5cec13dda9a 100644 --- a/extensions/qqbot/src/utils/text-parsing.ts +++ b/extensions/qqbot/src/utils/text-parsing.ts @@ -5,9 +5,9 @@ import type { RefAttachmentSummary } from "../ref-index-store.js"; const MAX_FACE_EXT_BYTES = 64 * 1024; /** Replace QQ face tags with readable text labels. */ -export function parseFaceTags(text: string): string { +export function parseFaceTags(text: string | undefined | null): string { if (!text) { - return text; + return ""; } return text.replace(//g, (_match, ext: string) => { @@ -26,9 +26,9 @@ export function parseFaceTags(text: string): string { } /** Remove internal framework markers before sending text outward. */ -export function filterInternalMarkers(text: string): string { +export function filterInternalMarkers(text: string | undefined | null): string { if (!text) { - return text; + return ""; } let result = text.replace(/\[\[[a-z_]+:\s*[^\]]*\]\]/gi, "");