Skip to content

Natural-language skill triggers are stored in triggers: but not folded into descriptions #2118

Description

@jinzheio

What I observed

The README says gstack skills support natural/voice-friendly trigger phrases:

gstack skills have voice-friendly trigger phrases. Say what you want naturally — "run a security check", "test the website", "do an engineering review" — and the right skill activates.

Source: README.md, Voice input section.

But most skill templates declare those phrases in a normal triggers: frontmatter field, while the generation pipeline only folds voice-triggers: into description:.

On current main (11de390b, v1.58.5.0), I counted:

  • 56 SKILL.md.tmpl files with triggers:
  • 20 SKILL.md.tmpl files with voice-triggers:
  • 0 code paths that fold ordinary triggers: into description:

The implemented folding path is specific to voice-triggers:

  • scripts/gen-skill-docs.ts has extractVoiceTriggers() / processVoiceTriggers().
  • processVoiceTriggers() appends Voice triggers (speech-to-text aliases): ... to the description.
  • The function returns unchanged when there is no voice-triggers: field.

That means phrases under normal triggers: stay as metadata. For hosts that discover skills from name + description, those phrases are not part of the matching surface.

Why this matters

Claude/OpenAI-style skill discovery appears to rely on name + description, not arbitrary extra frontmatter fields.

The repo already reflects that model in the Codex host config:

frontmatter: {
  mode: 'allowlist',
  keepFields: ['name', 'description'],
  descriptionLimit: 1024,
  descriptionLimitBehavior: 'error',
}

So for Codex/OpenAI output, triggers: is explicitly dropped. For Claude Code, even if the raw field remains in the file, the always-loaded catalog/triggering surface is still the description. The repo's own catalog trim also moves routing prose out of frontmatter after the lead sentence.

This creates a mismatch:

  • README promises natural phrases activate the right skill.
  • Templates contain those phrases, but mostly under triggers:.
  • The generator only preserves voice-triggers: in description:.
  • External host metadata uses the post-processed description, so ordinary triggers are invisible there too.

Concrete example

qa/SKILL.md.tmpl has useful triggers like:

triggers:
  - qa test this
  - find bugs on site
  - test the site
voice-triggers:
  - quality check
  - test the app
  - run QA

Only the voice-triggers values are appended to the description. The normal triggers values are not.

Expected behavior

Either:

  1. Fold selected triggers: phrases into the generated description: / Codex openai.yaml short description, similar to voice-triggers, or
  2. Rename the intended model-visible field and migrate templates so trigger phrases that should affect skill selection live in description: / voice-triggers:, or
  3. Document that triggers: is only for gstack's own router/browser-skill metadata and does not affect Claude/Codex skill discovery.

My guess is option 1 was intended, because processVoiceTriggers() already implements the exact mechanism, but only for voice-triggers:.

Suggested test coverage

Add a generator test that fails if a skill template has triggers: but the generated host-visible description/metadata has none of the expected trigger intent. A minimal version could assert that a fixture with triggers: emits a Use when asked to... line in generated Codex/Claude descriptions, subject to the existing description length limit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions