summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAiden Cline <[email protected]>2026-01-28 23:40:59 -0600
committerGitHub <[email protected]>2026-01-28 23:40:59 -0600
commit33c5c100ff271f639f998079e380b3957713b63c (patch)
tree5ef5262286d66af0218abb6a59ffe483b67e52f9
parent0fabdccf11aeaf62d60f6be3c10869283021ffb9 (diff)
downloadopencode-33c5c100ff271f639f998079e380b3957713b63c.tar.gz
opencode-33c5c100ff271f639f998079e380b3957713b63c.zip
fix: frontmatter was adding newlines in some cases causing invalid model ids (#11095)
Co-authored-by: aptdnfapt <[email protected]>
-rw-r--r--packages/opencode/src/config/markdown.ts29
-rw-r--r--packages/opencode/test/config/fixtures/markdown-header.md11
-rw-r--r--packages/opencode/test/config/fixtures/weird-model-id.md13
-rw-r--r--packages/opencode/test/config/markdown.test.ts46
4 files changed, 82 insertions, 17 deletions
diff --git a/packages/opencode/src/config/markdown.ts b/packages/opencode/src/config/markdown.ts
index d1eeeac38..4cd17746c 100644
--- a/packages/opencode/src/config/markdown.ts
+++ b/packages/opencode/src/config/markdown.ts
@@ -14,7 +14,9 @@ export namespace ConfigMarkdown {
return Array.from(template.matchAll(SHELL_REGEX))
}
- export function preprocessFrontmatter(content: string): string {
+ // other coding agents like claude code allow invalid yaml in their
+ // frontmatter, we need to fallback to a more permissive parser for those cases
+ export function fallbackSanitization(content: string): string {
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)
if (!match) return content
@@ -53,7 +55,7 @@ export namespace ConfigMarkdown {
// if value contains a colon, convert to block scalar
if (value.includes(":")) {
- result.push(`${key}: |`)
+ result.push(`${key}: |-`)
result.push(` ${value}`)
continue
}
@@ -66,20 +68,23 @@ export namespace ConfigMarkdown {
}
export async function parse(filePath: string) {
- const raw = await Bun.file(filePath).text()
- const template = preprocessFrontmatter(raw)
+ const template = await Bun.file(filePath).text()
try {
const md = matter(template)
return md
- } catch (err) {
- throw new FrontmatterError(
- {
- path: filePath,
- message: `${filePath}: Failed to parse YAML frontmatter: ${err instanceof Error ? err.message : String(err)}`,
- },
- { cause: err },
- )
+ } catch {
+ try {
+ return matter(fallbackSanitization(template))
+ } catch (err) {
+ throw new FrontmatterError(
+ {
+ path: filePath,
+ message: `${filePath}: Failed to parse YAML frontmatter: ${err instanceof Error ? err.message : String(err)}`,
+ },
+ { cause: err },
+ )
+ }
}
}
diff --git a/packages/opencode/test/config/fixtures/markdown-header.md b/packages/opencode/test/config/fixtures/markdown-header.md
new file mode 100644
index 000000000..d5af1f1c2
--- /dev/null
+++ b/packages/opencode/test/config/fixtures/markdown-header.md
@@ -0,0 +1,11 @@
+# Response Formatting Requirements
+
+Always structure your responses using clear markdown formatting:
+
+- By default don't put information into tables for questions (but do put information into tables when creating or updating files)
+- Use headings (##, ###) to organise sections, always
+- Use bullet points or numbered lists for multiple items
+- Use code blocks with language tags for any code
+- Use **bold** for key terms and emphasis
+- Use tables when comparing options or listing structured data
+- Break long responses into logical sections with headings
diff --git a/packages/opencode/test/config/fixtures/weird-model-id.md b/packages/opencode/test/config/fixtures/weird-model-id.md
new file mode 100644
index 000000000..bb02b0650
--- /dev/null
+++ b/packages/opencode/test/config/fixtures/weird-model-id.md
@@ -0,0 +1,13 @@
+---
+description: General coding and planning agent
+mode: subagent
+model: synthetic/hf:zai-org/GLM-4.7
+tools:
+ write: true
+ read: true
+ edit: true
+stuff: >
+ This is some stuff
+---
+
+Strictly follow da rules
diff --git a/packages/opencode/test/config/markdown.test.ts b/packages/opencode/test/config/markdown.test.ts
index b4263ee6b..c6133317e 100644
--- a/packages/opencode/test/config/markdown.test.ts
+++ b/packages/opencode/test/config/markdown.test.ts
@@ -104,7 +104,7 @@ describe("ConfigMarkdown: frontmatter parsing", async () => {
})
test("should extract occupation field with colon in value", () => {
- expect(parsed.data.occupation).toBe("This man has the following occupation: Software Engineer\n")
+ expect(parsed.data.occupation).toBe("This man has the following occupation: Software Engineer")
})
test("should extract title field with single quotes", () => {
@@ -128,15 +128,15 @@ describe("ConfigMarkdown: frontmatter parsing", async () => {
})
test("should extract URL with port", () => {
- expect(parsed.data.url).toBe("https://example.com:8080/path?query=value\n")
+ expect(parsed.data.url).toBe("https://example.com:8080/path?query=value")
})
test("should extract time with colons", () => {
- expect(parsed.data.time).toBe("The time is 12:30:00 PM\n")
+ expect(parsed.data.time).toBe("The time is 12:30:00 PM")
})
test("should extract value with multiple colons", () => {
- expect(parsed.data.nested).toBe("First: Second: Third: Fourth\n")
+ expect(parsed.data.nested).toBe("First: Second: Third: Fourth")
})
test("should preserve already double-quoted values with colons", () => {
@@ -148,7 +148,7 @@ describe("ConfigMarkdown: frontmatter parsing", async () => {
})
test("should extract value with quotes and colons mixed", () => {
- expect(parsed.data.mixed).toBe('He said "hello: world" and then left\n')
+ expect(parsed.data.mixed).toBe('He said "hello: world" and then left')
})
test("should handle empty values", () => {
@@ -190,3 +190,39 @@ describe("ConfigMarkdown: frontmatter parsing w/ no frontmatter", async () => {
expect(result.content.trim()).toBe("Content")
})
})
+
+describe("ConfigMarkdown: frontmatter parsing w/ Markdown header", async () => {
+ const result = await ConfigMarkdown.parse(import.meta.dir + "/fixtures/markdown-header.md")
+
+ test("should parse and match", () => {
+ expect(result).toBeDefined()
+ expect(result.data).toEqual({})
+ expect(result.content.trim()).toBe(`# Response Formatting Requirements
+
+Always structure your responses using clear markdown formatting:
+
+- By default don't put information into tables for questions (but do put information into tables when creating or updating files)
+- Use headings (##, ###) to organise sections, always
+- Use bullet points or numbered lists for multiple items
+- Use code blocks with language tags for any code
+- Use **bold** for key terms and emphasis
+- Use tables when comparing options or listing structured data
+- Break long responses into logical sections with headings`)
+ })
+})
+
+describe("ConfigMarkdown: frontmatter has weird model id", async () => {
+ const result = await ConfigMarkdown.parse(import.meta.dir + "/fixtures/weird-model-id.md")
+
+ test("should parse and match", () => {
+ expect(result).toBeDefined()
+ expect(result.data["description"]).toEqual("General coding and planning agent")
+ expect(result.data["mode"]).toEqual("subagent")
+ expect(result.data["model"]).toEqual("synthetic/hf:zai-org/GLM-4.7")
+ expect(result.data["tools"]["write"]).toBeTrue()
+ expect(result.data["tools"]["read"]).toBeTrue()
+ expect(result.data["stuff"]).toBe("This is some stuff\n")
+
+ expect(result.content.trim()).toBe("Strictly follow da rules")
+ })
+})