Skip to content

fix(den-api): parse YAML frontmatter in deriveProjection to prevent SKILL.md name/description mangling#2355

Open
adityadeshlahre wants to merge 1 commit into
different-ai:devfrom
adityadeshlahre:fix-yaml-frontmater-materialized
Open

fix(den-api): parse YAML frontmatter in deriveProjection to prevent SKILL.md name/description mangling#2355
adityadeshlahre wants to merge 1 commit into
different-ai:devfrom
adityadeshlahre:fix-yaml-frontmater-materialized

Conversation

@adityadeshlahre

@adityadeshlahre adityadeshlahre commented Jun 23, 2026

Copy link
Copy Markdown

Problem

When a member installs a shared skill from the org Marketplace, the materialized SKILL.md frontmatter is wrong:

  • name becomes the config-object id (cob_...) instead of the real skill name
  • The original name: line is nested into description
# Owner-created:
name: laptop-refresh
description: 4-step laptop refresh / ServiceNow request policy for the Sales team

# Member-installed (broken):
name: "cob-01kvshfchxfqc94qnmvanzsv4c"
description: "name: laptop-refresh"

Root Cause

deriveProjection() in ee/apps/den-api/src/routes/org/plugin-system/store.ts extracts title and description when creating config objects, but never parsed YAML frontmatter from rawSourceText for markdown-based types (skill, agent, command).

  • Title fell through to firstTextLine() → returned "---" → stored as title
  • Description scanned non-decorated lines after stripping ---"name: laptop-refresh" was the first hit

Fix

Parse YAML frontmatter using the existing parseMarkdownFrontmatter() helper and insert name/title and description/summary as high-priority candidates before the raw-text heuristics.

Reproduction Evidence

Before (broken) — config-object create response:

{
  "title": "---",
  "description": "name: laptop-refresh"
}

After (fixed) — config-object create response:

{
  "title": "laptop-refresh",
  "description": "4-step laptop refresh / ServiceNow request policy for the Sales team"
}

Review in cubic

@vercel

vercel Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openwork-landing Ready Ready Preview, Comment, Open in v0 Jun 23, 2026 10:14pm

@vercel

vercel Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

@adityadeshlahre is attempting to deploy a commit to the Different AI Team on Vercel.

A member of the Team first needs to authorize it.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

Re-trigger cubic

@adityadeshlahre

Copy link
Copy Markdown
Author

Reproduction (live API test)

Bug reproduction — create a config object with only rawSourceText (no metadata):

curl -X POST "http://localhost:8788/v1/config-objects" \
  -H "Cookie: better-auth.session_token=<session>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "skill",
    "sourceMode": "cloud",
    "input": {
      "rawSourceText": "---\nname: laptop-refresh\ndescription: 4-step laptop refresh / ServiceNow request policy for the Sales team\n---\n\n## Steps\n\n1. Check warranty status\n2. Submit ServiceNow request\n3. Schedule technician visit\n4. Confirm receipt"
    }
  }'

Before (broken deriveProjection):

{ "title": "---", "description": "name: laptop-refresh" }

After (fixed deriveProjection):

{ "title": "laptop-refresh", "description": "4-step laptop refresh / ServiceNow request policy for the Sales team" }

The fix also correctly handles the subsequent install flow — when installCloudPlugin() reads object.title = "laptop-refresh", the install path and generated frontmatter are correct.

@Pablosinyores

Copy link
Copy Markdown

Took a close read through this — the fix is at the right layer and reusing the file-local parseMarkdownFrontmatter (the same helper the GitHub-import path already uses) keeps the two create paths consistent without pulling in a new YAML dep. Candidate ordering looks right too: explicit metadata/payload still win, frontmatter slots in above the raw heuristic.

One residual edge I think is worth handling before merge:

Description still gets mangled when frontmatter has name: but no description:. The new frontmatterDescription is only added as a candidate — the heuristic fallback below it still scans the un-stripped rawSourceText:

const descriptionCandidate = [
  // ...
  typeof frontmatterDescription === "string" ? frontmatterDescription : null,
  rawSourceText
    ? rawSourceText.split(/\r?\n/g) /* ...scans the frontmatter lines... */
    : null,
]

A SKILL.md with name: but no description: (allowed) leaves frontmatterDescription undefined, so the scan falls through to the frontmatter block and picks up name: <value> as the description — the same class of bug this PR fixes for the title. Suggest destructuring the body once and feeding it to the fallback, mirroring the GitHub path which already passes parseMarkdownFrontmatter(...).body:

const { data: frontmatter, body } = rawSourceText
  ? parseMarkdownFrontmatter(rawSourceText)
  : { data: {}, body: "" }
// ...use `body` instead of `rawSourceText` in the firstTextLine / raw-scan branches

Two smaller notes:

  • deriveProjection has no direct test coverage, and this path was wrong in production — a unit test asserting {title, description} for a frontmatter SKILL.md (plus the no-description case above) would lock it in cheaply.
  • Minor: the file-local parser is case-sensitive on keys and treats |/> block scalars literally. There's a more robust exported parser in @openwork-ee/utils (parseFrontmatter); not needed for this fix, but a follow-up consolidating both paths onto it would be more correct.

Nothing blocking on the primary reported scenario — the title/description fix itself is solid.

@kilicrukiye784-lab

kilicrukiye784-lab commented Jun 28, 2026 via email

Copy link
Copy Markdown

@Pablosinyores Pablosinyores left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch on parsing the frontmatter. One thing on the title path though: frontmatterName is added after metadata.name/payload.name/metadata.title/payload.title in the titleCandidate array, and since .find returns the first truthy entry, whichever of those already carries the cob_... id will still win — so the name half of #2350 looks like it stays broken. Only the description half is actually fixed here, because frontmatterDescription is placed before the raw-source fallback.

The reported output (name: "cob-01k...") is the tell: one of metadata.title/name or payload.title/name is populated with the config-object id on member install, which outranks the new frontmatterName. Moving frontmatterName ahead of the metadata/payload name candidates — similar to how materializeGithubImportedObject strips frontmatter into projectionRawSource before deriving — should make the name resolve correctly too.

Worth adding a test that asserts the installed name equals the original skill name (laptop-refresh), not just the description, so the title path is actually covered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Installing a cloud skill mangles SKILL.md YAML frontmatter (name/description)

3 participants