From d5154fb74ea65542624c3345bd5a243a2369922e Mon Sep 17 00:00:00 2001 From: holny Date: Thu, 8 Oct 2026 00:05:37 +0800 Subject: [PATCH] fix(core): bypass gray-matter content cache when parsing frontmatter (#51245) --- packages/core/src/config/markdown.ts | 8 +++-- packages/core/test/config/markdown.test.ts | 35 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 packages/core/test/config/markdown.test.ts diff --git a/packages/core/src/config/markdown.ts b/packages/core/src/config/markdown.ts index 4887a502db7..8093ff7e735 100644 --- a/packages/core/src/config/markdown.ts +++ b/packages/core/src/config/markdown.ts @@ -2,10 +2,14 @@ export * as ConfigMarkdown from "./markdown.js" import matter from "gray-matter" export function parse(content: string) { + // Passing options bypasses gray-matter's module-global content cache, which + // it populates before parsing: a failed YAML parse poisons the entry and + // every later parse of the same content replays it without throwing, so the + // sanitize fallback below never runs. Upstream: jonschlinkert/gray-matter#166. try { - return matter(content) + return matter(content, {}) } catch { - return matter(sanitize(content)) + return matter(sanitize(content), {}) } } diff --git a/packages/core/test/config/markdown.test.ts b/packages/core/test/config/markdown.test.ts new file mode 100644 index 00000000000..d050c563869 --- /dev/null +++ b/packages/core/test/config/markdown.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, test } from "bun:test" +import { ConfigMarkdown } from "@opencode/core/config/markdown" + +const invalidYaml = `--- +description: Use when the user needs to crawl pages. Keywords: crawl, scrape +--- +body` + +describe("ConfigMarkdown.parse", () => { + test("recovers unquoted-colon frontmatter via the sanitize fallback", () => { + const parsed = ConfigMarkdown.parse(invalidYaml) + expect(parsed.data.description).toBe("Use when the user needs to crawl pages. Keywords: crawl, scrape") + expect(parsed.content.trim()).toBe("body") + }) + + test("recovers the same content again after a previous failed parse", () => { + // gray-matter caches by content before parsing; a poisoned entry used to + // make every later parse of the same text return silently without data. + expect(() => ConfigMarkdown.parse("---\ndescription: [unclosed\n---\nbody")).toThrow() + const parsed = ConfigMarkdown.parse(invalidYaml) + expect(parsed.data.description).toBe("Use when the user needs to crawl pages. Keywords: crawl, scrape") + }) + + test("keeps throwing for the same unparseable content on every call", () => { + const input = "---\ndescription: [unclosed\n---\nbody" + expect(() => ConfigMarkdown.parse(input)).toThrow() + expect(() => ConfigMarkdown.parse(input)).toThrow() + }) + + test("parses plain content without frontmatter", () => { + const parsed = ConfigMarkdown.parse("just body") + expect(parsed.content).toBe("just body") + expect(parsed.data).toEqual({}) + }) +}) \ No newline at end of file