{"record":{"id":"bb4efde1c675b59b","repo":"TencentCloud/TencentDB-Agent-Memory","slug":"invalid-frontmatter","errorCode":"INVALID_FRONTMATTER","errorMessage":"frontmatter.name '${file.frontmatter.name}' != body.name '${input.name}'","messagePattern":"frontmatter\\.name '(.+?)' != body\\.name '(.+?)'","errorType":"error_code","errorClass":"SkillCoreError","httpStatus":null,"severity":"error","filePath":"MemoryCore/src/core/skill/skill-core.ts","lineNumber":256,"sourceCode":"    this.onSkillArchived = opts.onSkillArchived;\n    this.onSkillAccessed = opts.onSkillAccessed;\n  }\n\n  /** 读路径读到具体 skill 后 fire。异常吞掉，不阻塞读。 */\n  private notifyAccessed(skill: Skill): void {\n    if (!this.onSkillAccessed) return;\n    try { this.onSkillAccessed(skill); } catch { /* swallow */ }\n  }\n\n  // ───────────────────────────────────────────────────────────────────\n  //  WRITE actions\n  // ───────────────────────────────────────────────────────────────────\n\n  async create(input: CreateInput): Promise<Skill> {\n    // 1) parse + validate\n    const file = this.parseAndValidate(input.content);\n    if (file.frontmatter.name !== input.name) {\n      throw new SkillCoreError(\"INVALID_FRONTMATTER\", `frontmatter.name '${file.frontmatter.name}' != body.name '${input.name}'`);\n    }\n\n    // 2) 生成 sid 并做碰撞检测\n    //\n    // 背景：默认 ulid 走 CSPRNG base62 12 字符（~71 bit 真熵，单实例 100 万\n    // skill 时碰撞概率 ~1.5e-10），工程上\"永远不会撞\"；但仍加一层 preflight\n    // 防御，把\"撞了静默覆盖\"变成\"撞了 retry\"。为什么不上 DB UNIQUE 约束：\n    //   - SQLite skills 表 UNIQUE(skill_id, version) 已存在，v1 碰撞会被物理挡住\n    //   - TCVDB 主键是 row_id（每行唯一），无法给 skill_id 加\"仅 v1 唯一\"约束\n    //     （skill 天生多版本，version 2/3 就是同 skill_id 共存）\n    // → 应用层 preflight 是唯一可移植到两种 store 的方案。\n    //\n    // 注：注入的 ulid 工厂可能不带 'skl-' 前缀，这里兜底拼上。\n    const MAX_ID_ATTEMPTS = 3;\n    let sid = \"\";\n    for (let attempt = 1; attempt <= MAX_ID_ATTEMPTS; attempt++) {\n      const u = this.ulid();\n      sid = u.startsWith(\"skl-\") ? u : `skl-${u}`;","sourceCodeStart":238,"sourceCodeEnd":274,"githubUrl":"https://github.com/TencentCloud/TencentDB-Agent-Memory/blob/3efcd317b84146d6a08518ac0f7ee7c8a8d200ec/MemoryCore/src/core/skill/skill-core.ts#L238-L274","documentation":"SkillCore.create parses the provided skill markdown and requires consistency between the YAML frontmatter \"name\" field and the \"name\" supplied in the request body. If they differ it throws SkillCoreError INVALID_FRONTMATTER, because the frontmatter name is the skill's identity and a mismatch would create ambiguous or drifting identities.","triggerScenarios":"Calling skillCore.create({ name: \"code-review\", content: \"---\\nname: code-reviewer\\n...\" }) — frontmatter name != input.name. Typically the content was copied from another skill, renamed in one place only, or generated by a template with a placeholder name.","commonSituations":"Renaming a skill in the body but forgetting the frontmatter; copying an existing skill's markdown as a starting point; programmatic content generation where the name is interpolated into the body but not the frontmatter (or vice versa).","solutions":["Make frontmatter.name match input.name exactly (or vice versa) before calling create","Derive one from the other programmatically instead of maintaining two literals","Add a pre-submit check in your tooling that parses frontmatter and compares with the body name"],"exampleFix":"// before\nawait core.create({ name: \"code-review\", content: \"---\\nname: code-reviewer\\n---\\n...\" });\n// after\nconst name = \"code-review\";\nawait core.create({ name, content: `---\\nname: ${name}\\n---\\n...` });","handlingStrategy":"validation","validationCode":"import { parse as parseYaml } from \"yaml\";\nfunction assertFrontmatterNameMatches(name: string, content: string): void {\n  const m = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n  if (!m) throw new Error(\"skill content missing frontmatter\");\n  const fm = parseYaml(m[1]) as { name?: string };\n  if (fm.name !== name)\n    throw new Error(`frontmatter.name '${fm.name}' must equal body.name '${name}'`);\n}\nassertFrontmatterNameMatches(input.name, input.content); // call before core.create","typeGuard":"function frontmatterNameMatches(name: string, content: string): boolean {\n  const m = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n  if (!m) return false;\n  return (parseYaml(m[1]) as { name?: string })?.name === name;\n}","tryCatchPattern":"try {\n  await core.create(input);\n} catch (e) {\n  if (isSkillCoreError(e) && e.code === \"INVALID_FRONTMATTER\") {\n    // auto-heal: rewrite frontmatter name to match the body name\n    input.content = input.content.replace(/^(---\\nname: ).*(\\n---)/, `$1${input.name}$2`);\n    return core.create(input);\n  }\n  throw e;\n}","preventionTips":["Generate skill markdown from a single template that interpolates the name in both places","Never hand-copy frontmatter from another skill when creating","Add a CI/pre-submit lint that compares frontmatter.name with the API body name"],"tags":["validation","frontmatter","yaml","skill-creation"],"backgroundTag":"schema-validation-failed","analyzedSha":"3efcd317b84146d6a08518ac0f7ee7c8a8d200ec","analyzedAt":"2026-09-01T05:44:22.276Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}