fix: guard against undefined event.content in cron agentTurn payload (#66302)

* 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 <namkhanh2172@gmail.com>
Co-authored-by: sliverp <870080352@qq.com>
This commit is contained in:
xinmotlanthua
2026-04-15 10:47:21 +07:00
committed by GitHub
parent fb92ca1a4d
commit 90c06c04c8
3 changed files with 17 additions and 4 deletions

View File

@@ -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=<ts>` 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

View File

@@ -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 = `<faceType=1,faceId="1",ext="${oversizedBase64}">`;

View File

@@ -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(/<faceType=\d+,faceId="[^"]*",ext="([^"]*)">/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, "");