Skip to content

Skill frontmatter parser silently drops triggers (block sequences → empty, inline → quote-polluted) #1970

Closed

Description

@bradygaster

Summary

parseFrontmatter (packages/squad-sdk/src/skills/skill-loader.ts:48) is a naive line-based parser that only understands inline arrays. Two consequences, both of which silently disable skill discovery — no error, no warning, skill just never matches.

Found while strengthening a test in #1946 (the weak assertion there had been masking it).

Defect 1 — YAML block sequences produce no triggers

triggers:
  - "set up Squad agentic workflows"

The triggers: line has an empty post-colon value, so the parser stores the empty string. parseSkillFile then does Array.isArray(meta.triggers) ? meta.triggers : [] (skill-loader.ts:131) → []. The - "…" lines contain no colon and are skipped entirely.

Defect 2 — inline arrays retain quote characters

The inline branch splits on , and trims, but never strips quotes, so triggers come back as "\"squad commands\"" — with literal " characters that will never match user text.

Measured

block         -> ""                     (undiscoverable)
inline+quoted -> ["\"a phrase\"", ...]  (quotes retained — never matches)
inline bare   -> ["a phrase", ...]      (correct)

Affected skills

27 skills exist; only 3 declare triggers at all:

Skill Form Runtime triggers
.squad/skills/cross-squad/SKILL.md block [] — fully undiscoverable
.squad/skills/gh-aw-enlistment/SKILL.md block fixed in #1946 by converting to bare inline
.squad/skills/squad-commands/SKILL.md inline + quoted quote-polluted, will not match

So every skill that declares triggers is currently mis-parsed, and #1946 worked around it locally rather than fixing the parser.

This is the array the runtime matcher iterates (packages/squad-sdk/src/skills/index.ts:75), so the impact is real skill-matching failure, not cosmetic.

Reproduce

const { parseFrontmatter } = require('packages/squad-sdk/dist/skills/skill-loader.js');
const t = (l, fm) => console.log(l, '->', JSON.stringify(parseFrontmatter('---\n'+fm+'\n---\nbody').meta.triggers));
t('block      ', 'triggers:\n  - "a phrase"');
t('inline+quot', 'triggers: ["a phrase"]');
t('inline bare', 'triggers: [a phrase]');

Blast radius

parseFrontmatter is shared — skill-source.ts calls it in 6 places (lines 68, 87, 107, 160, 177, 195). There is no real YAML parser anywhere in this path, so fixing it changes behavior for all skill loading. Notably, fixing quote-stripping will change squad-commands' runtime triggers from quoted to unquoted, which is the intended correction but is still a behavior change worth calling out in review.

Suggested fix

  1. Support block sequences: after a key with an empty value, consume following - item lines into an array.
  2. Strip surrounding matched quotes from scalar and array values.
  3. Convert cross-squad and squad-commands to whatever form is then canonical, and pick one documented form for skill authors.
  4. Add a guard asserting that every skill declaring triggers: parses to a non-empty array — that single assertion would have caught all of this.

Acceptance criteria

  • Block-sequence triggers: parses to a non-empty string array
  • Quotes stripped from both scalar and array frontmatter values
  • cross-squad and squad-commands verified discoverable at runtime
  • Guard test fails if any skill's declared triggers parse to []
  • Existing 27 skills still load; no regression in skill-source.ts consumers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions