{"record":{"id":"b3d184570f03f875","repo":"Yeachan-Heo/oh-my-codex","slug":"filepath-frontmatter-key-has-an-unterminat","errorCode":null,"errorMessage":"${filePath} frontmatter \"${key}\" has an unterminated quoted string","messagePattern":"(.+?) frontmatter \"(.+?)\" has an unterminated quoted string","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/cli/setup.ts","lineNumber":2226,"sourceCode":"function parseSkillFrontmatterScalar(\n\tvalue: string,\n\tkey: string,\n\tfilePath: string,\n): string {\n\tconst trimmed = value.trim();\n\tif (!trimmed) {\n\t\tthrow new Error(`${filePath} frontmatter \"${key}\" must not be empty`);\n\t}\n\tif (trimmed === \"|\" || trimmed === \">\") {\n\t\tthrow new Error(\n\t\t\t`${filePath} frontmatter \"${key}\" must be a single-line string`,\n\t\t);\n\t}\n\n\tconst quote = trimmed[0];\n\tif (quote === '\"' || quote === \"'\") {\n\t\tif (trimmed.length < 2 || trimmed.at(-1) !== quote) {\n\t\t\tthrow new Error(\n\t\t\t\t`${filePath} frontmatter \"${key}\" has an unterminated quoted string`,\n\t\t\t);\n\t\t}\n\t\tconst unquoted = trimmed.slice(1, -1).trim();\n\t\tif (!unquoted) {\n\t\t\tthrow new Error(`${filePath} frontmatter \"${key}\" must not be empty`);\n\t\t}\n\t\treturn unquoted;\n\t}\n\n\tconst unquoted = trimmed.replace(/\\s+#.*$/, \"\").trim();\n\tif (!unquoted) {\n\t\tthrow new Error(`${filePath} frontmatter \"${key}\" must not be empty`);\n\t}\n\treturn unquoted;\n}\n\nexport function parseSkillFrontmatter(","sourceCodeStart":2208,"sourceCodeEnd":2244,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/cli/setup.ts#L2208-L2244","documentation":"A frontmatter value starting with a quote must end with the same quote after at least 2 characters. This error fires for unterminated quoted strings, e.g. \"my-skill (missing closing quote).","triggerScenarios":"Values like 'name: \"my-skill' or 'description: it's fine' where an opening double/single quote is not matched by a closing one at the end of the trimmed value.","commonSituations":"Apostrophes at the start of descriptions, smart quotes pasted from word processors, or simply forgetting the closing quote.","solutions":["Close the quoted string: name: \"my-skill\"","If the leading character is not meant as a quote (e.g. it's), rephrase or wrap the whole value in matched quotes","Avoid smart/curly quotes; use plain ASCII quotes"],"exampleFix":"# before\nname: \"my-skill\n# after\nname: \"my-skill\"","handlingStrategy":"validation","validationCode":"const line = 'name: \"my-skill';\nconst v = line.replace(/^[^:]*:\\s*/, \"\").trim();\nconst q = v[0];\nconst terminated = !(q === '\"' || q === \"'\") || (v.length >= 2 && v.at(-1) === q);\nif (!terminated) throw new Error(\"unterminated quoted frontmatter value\");","typeGuard":"const isTerminatedQuoted = (v: string): boolean => {\n  const q = v.trim()[0];\n  if (q !== '\"' && q !== \"'\") return true;\n  const t = v.trim();\n  return t.length >= 2 && t.at(-1) === q;\n};","tryCatchPattern":"try { parseSkillFrontmatter(content); } catch (e) { if (e instanceof Error && e.message.includes(\"unterminated quoted string\")) { /* add the matching closing quote on the flagged key */ } else throw e; }","preventionTips":["Always close quotes in pairs","Avoid leading apostrophes in unquoted values","Use plain ASCII quotes, not smart quotes"],"tags":["yaml","frontmatter","skill","quoting","validation"],"backgroundTag":"config-validation-failed","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}