From 25ca2fcda43c87c87aedbec6851fe03ea18db0a3 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 2 May 2026 04:41:45 +0100 Subject: [PATCH] fix(media): trim json suffixes from media paths --- CHANGELOG.md | 1 + src/media/parse.test.ts | 9 +++++++++ src/media/parse.ts | 6 +++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4abe1a295a5..74a93fa949b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Gateway/chat history: merge Claude CLI transcript imports for Anthropic-routed sessions that still have a Claude CLI binding, so local chat history does not hide CLI JSONL turns. Fixes #75850. Thanks @alfredjbclaw. +- Media: trim serialized JSON suffixes after local `MEDIA:` directive file extensions, so generated-image metadata cannot pollute the parsed media path and cause false `ENOENT` delivery failures. Fixes #75182. Thanks @TnzGit and @hclsys. - Cron: make scheduler reload schedule comparison tolerate malformed persisted jobs, so one bad cron entry no longer aborts the whole tick. Fixes #75886. Thanks @samfox-ai. - Doctor/channels: warn after migrations when default Telegram or Discord accounts have no configured token and their env fallback (`TELEGRAM_BOT_TOKEN` or `DISCORD_BOT_TOKEN`) is unavailable, with secret-safe migration docs for checking state-dir `.env`. Fixes #74298. Thanks @lolaopenclaw. - Control UI/chat: keep live replies visible when a raw session alias such as `main` sends the chat turn but Gateway emits events under the canonical session key for the same run. Fixes #73716. Thanks @teebes. diff --git a/src/media/parse.test.ts b/src/media/parse.test.ts index a01f931a2a7..e99eb935c40 100644 --- a/src/media/parse.test.ts +++ b/src/media/parse.test.ts @@ -54,6 +54,15 @@ describe("splitMediaFromOutput", () => { ["C:\\Users\\pete\\Pictures\\snap.png", "MEDIA:C:\\Users\\pete\\Pictures\\snap.png"], ["/tmp/tts-fAJy8C/voice-1770246885083.opus", "MEDIA:/tmp/tts-fAJy8C/voice-1770246885083.opus"], ["image.png", "MEDIA:image.png"], + [ + "/path/to/image.png", + 'MEDIA:/path/to/image.png"}],"details":{"provider":"openai","model":"gpt-image-2"}', + ], + [ + "/path/to/image.png", + String.raw`MEDIA:/path/to/image.png\"}],\"details\":{\"provider\":\"openai\"}`, + ], + ["/tmp/render,final.png", "MEDIA:/tmp/render,final.png"], ] as const)("accepts supported media path variant: %s", (expectedPath, input) => { expectAcceptedMediaPathCase(expectedPath, input); }); diff --git a/src/media/parse.ts b/src/media/parse.ts index 9d858f2e272..eb79586f751 100644 --- a/src/media/parse.ts +++ b/src/media/parse.ts @@ -34,8 +34,12 @@ export function normalizeMediaSource(src: string) { return src.startsWith("file://") ? src.replace("file://", "") : src; } +const TRAILING_SERIALIZED_JSON_AFTER_EXT_RE = /^(.*\.\w{1,10})\\?"(?=[\]},:,]|$).*/s; + function cleanCandidate(raw: string) { - return raw.replace(/^[`"'[{(]+/, "").replace(/[`"'\\})\],]+$/, ""); + const stripped = raw.replace(/^[`"'[{(]+/, "").replace(/[`"'\\})\],]+$/, ""); + const jsonSuffixMatch = TRAILING_SERIALIZED_JSON_AFTER_EXT_RE.exec(stripped); + return jsonSuffixMatch?.[1] ?? stripped; } const WINDOWS_DRIVE_RE = /^[a-zA-Z]:[\\/]/;