From 246d2f12a727052d88f882fa961307506fb139cf Mon Sep 17 00:00:00 2001 From: Rachel Lee Nabors Date: Tue, 20 Jan 2026 17:19:48 +0000 Subject: [PATCH] Fix editorial workflow adding code fences to files LLMs often wrap their markdown output in ```mdx or ```markdown code fences, even when instructed to return only the content. This caused the editorial review workflow to commit files with spurious code fence markers at the beginning and end. Added stripCodeFences() helper that removes these markers from only the start and end of responses, preserving any legitimate code blocks within the content. Fixes issue seen in PR #673. Co-Authored-By: Claude Opus 4.5 --- scripts/vale-editorial.ts | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/scripts/vale-editorial.ts b/scripts/vale-editorial.ts index f2daa1f89..46a5d799c 100644 --- a/scripts/vale-editorial.ts +++ b/scripts/vale-editorial.ts @@ -46,6 +46,8 @@ const MAX_AI_TOKENS = 8192; const OWNER = "ArcadeAI"; const REPO = "docs"; const EDITORIAL_COMMENT_REGEX = //; +const CODE_FENCE_OPEN_REGEX = /^```(?:mdx?|markdown)?\n/; +const CODE_FENCE_CLOSE_REGEX = /\n```$/; const HTTP_UNPROCESSABLE_ENTITY = 422; const MEGABYTE = KILOBYTE * KILOBYTE; const MAX_BUFFER_MB = 10; @@ -84,6 +86,15 @@ type ValeIssue = { type ValeOutput = Record; +// Strip markdown code fences from the beginning and end of LLM responses +// LLMs often wrap their output in ```mdx or ```markdown blocks +function stripCodeFences(content: string): string { + let result = content.trim(); + result = result.replace(CODE_FENCE_OPEN_REGEX, ""); + result = result.replace(CODE_FENCE_CLOSE_REGEX, ""); + return result; +} + // Run Vale on content and return issues function runValeOnContent(filename: string, content: string): ValeIssue[] { // Create temp directory and file with correct extension @@ -155,7 +166,9 @@ async function fixValeIssues( messages: [{ role: "user", content: prompt }], }); const textBlock = response.content.find((b) => b.type === "text"); - return textBlock?.type === "text" ? textBlock.text : content; + return textBlock?.type === "text" + ? stripCodeFences(textBlock.text) + : content; } const response = await ai.client.chat.completions.create({ @@ -163,7 +176,8 @@ async function fixValeIssues( max_tokens: MAX_AI_TOKENS, messages: [{ role: "user", content: prompt }], }); - return response.choices[0]?.message?.content ?? content; + const result = response.choices[0]?.message?.content; + return result ? stripCodeFences(result) : content; } catch (error) { console.error("Error fixing Vale issues:", error); return content; @@ -266,7 +280,7 @@ async function getEditorialFromAnthropic( return null; } - const revisedContent = textBlock.text.trim(); + const revisedContent = stripCodeFences(textBlock.text); if (revisedContent === "NO_CHANGES_NEEDED") { return null; @@ -298,8 +312,12 @@ async function getEditorialFromOpenAI( messages: [{ role: "user", content: prompt }], }); - const revisedContent = response.choices[0]?.message?.content?.trim(); - if (!revisedContent || revisedContent === "NO_CHANGES_NEEDED") { + const rawContent = response.choices[0]?.message?.content; + if (!rawContent) { + return null; + } + const revisedContent = stripCodeFences(rawContent); + if (revisedContent === "NO_CHANGES_NEEDED") { return null; }