summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAiden Cline <[email protected]>2025-10-30 10:56:30 -0500
committerAiden Cline <[email protected]>2025-10-30 10:56:40 -0500
commit42e0b47a7db1ce06f422211ed3818c49474d4614 (patch)
tree1cb3a708df48ff732525dc5c6173505fecedeaa3
parent2d5df3ad7671f3fe6d7f46b65218f6901c77e9d6 (diff)
downloadopencode-42e0b47a7db1ce06f422211ed3818c49474d4614.tar.gz
opencode-42e0b47a7db1ce06f422211ed3818c49474d4614.zip
fix: better frontmatter errors
-rw-r--r--packages/opencode/src/cli/error.ts13
-rw-r--r--packages/opencode/src/config/config.ts11
-rw-r--r--packages/opencode/src/config/markdown.ts29
3 files changed, 43 insertions, 10 deletions
diff --git a/packages/opencode/src/cli/error.ts b/packages/opencode/src/cli/error.ts
index 1bc20de32..7c873ae50 100644
--- a/packages/opencode/src/cli/error.ts
+++ b/packages/opencode/src/cli/error.ts
@@ -1,3 +1,4 @@
+import { ConfigMarkdown } from "@/config/markdown"
import { Config } from "../config/config"
import { MCP } from "../mcp"
import { UI } from "./ui"
@@ -7,16 +8,22 @@ export function FormatError(input: unknown) {
return `MCP server "${input.data.name}" failed. Note, opencode does not support MCP authentication yet.`
if (Config.JsonError.isInstance(input)) {
return (
- `Config file at ${input.data.path} is not valid JSON(C)` + (input.data.message ? `: ${input.data.message}` : "")
+ `Config file at ${input.data.path} is not valid JSON(C)` +
+ (input.data.message ? `: ${input.data.message}` : "")
)
}
if (Config.ConfigDirectoryTypoError.isInstance(input)) {
return `Directory "${input.data.dir}" in ${input.data.path} is not valid. Use "${input.data.suggestion}" instead. This is a common typo.`
}
+ if (ConfigMarkdown.FrontmatterError.isInstance(input)) {
+ return `Failed to parse frontmatter in ${input.data.path}:\n${input.data.message}`
+ }
if (Config.InvalidError.isInstance(input))
return [
- `Config file at ${input.data.path} is invalid` + (input.data.message ? `: ${input.data.message}` : ""),
- ...(input.data.issues?.map((issue) => "↳ " + issue.message + " " + issue.path.join(".")) ?? []),
+ `Config file at ${input.data.path} is invalid` +
+ (input.data.message ? `: ${input.data.message}` : ""),
+ ...(input.data.issues?.map((issue) => "↳ " + issue.message + " " + issue.path.join(".")) ??
+ []),
].join("\n")
if (UI.CancelledError.isInstance(input)) return ""
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index 12a5c1624..031c4d82a 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -9,7 +9,6 @@ import { Global } from "../global"
import fs from "fs/promises"
import { lazy } from "../util/lazy"
import { NamedError } from "../util/error"
-import matter from "gray-matter"
import { Flag } from "../flag/flag"
import { Auth } from "../auth"
import {
@@ -21,6 +20,7 @@ import { Instance } from "../project/instance"
import { LSPServer } from "../lsp/server"
import { BunProc } from "@/bun"
import { Installation } from "@/installation"
+import { ConfigMarkdown } from "./markdown"
export namespace Config {
const log = Log.create({ service: "config" })
@@ -191,8 +191,7 @@ export namespace Config {
dot: true,
cwd: dir,
})) {
- const content = await Bun.file(item).text()
- const md = matter(content)
+ const md = await ConfigMarkdown.parse(item)
if (!md.data) continue
const name = (() => {
@@ -231,8 +230,7 @@ export namespace Config {
dot: true,
cwd: dir,
})) {
- const content = await Bun.file(item).text()
- const md = matter(content)
+ const md = await ConfigMarkdown.parse(item)
if (!md.data) continue
// Extract relative path from agent folder for nested agents
@@ -274,8 +272,7 @@ export namespace Config {
dot: true,
cwd: dir,
})) {
- const content = await Bun.file(item).text()
- const md = matter(content)
+ const md = await ConfigMarkdown.parse(item)
if (!md.data) continue
const config = {
diff --git a/packages/opencode/src/config/markdown.ts b/packages/opencode/src/config/markdown.ts
index a4dcbf5d4..3e84bbf43 100644
--- a/packages/opencode/src/config/markdown.ts
+++ b/packages/opencode/src/config/markdown.ts
@@ -1,3 +1,7 @@
+import { NamedError } from "@/util/error"
+import matter from "gray-matter"
+import { z } from "zod"
+
export namespace ConfigMarkdown {
export const FILE_REGEX = /(?<![\w`])@(\.?[^\s`,.]*(?:\.[^\s`,.]+)*)/g
export const SHELL_REGEX = /!`([^`]+)`/g
@@ -9,4 +13,29 @@ export namespace ConfigMarkdown {
export function shell(template: string) {
return Array.from(template.matchAll(SHELL_REGEX))
}
+
+ export async function parse(filePath: string) {
+ const template = await Bun.file(filePath).text()
+
+ try {
+ const md = matter(template)
+ return md
+ } catch (err) {
+ throw new FrontmatterError(
+ {
+ path: filePath,
+ message: `Failed to parse YAML frontmatter: ${err instanceof Error ? err.message : String(err)}`,
+ },
+ { cause: err },
+ )
+ }
+ }
+
+ export const FrontmatterError = NamedError.create(
+ "ConfigFrontmatterError",
+ z.object({
+ path: z.string(),
+ message: z.string(),
+ }),
+ )
}