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:
- Fold selected
triggers: phrases into the generated description: / Codex openai.yaml short description, similar to voice-triggers, or
- Rename the intended model-visible field and migrate templates so trigger phrases that should affect skill selection live in
description: / voice-triggers:, or
- 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.
What I observed
The README says gstack skills support natural/voice-friendly trigger phrases:
Source:
README.md, Voice input section.But most skill templates declare those phrases in a normal
triggers:frontmatter field, while the generation pipeline only foldsvoice-triggers:intodescription:.On current
main(11de390b, v1.58.5.0), I counted:SKILL.md.tmplfiles withtriggers:SKILL.md.tmplfiles withvoice-triggers:triggers:intodescription:The implemented folding path is specific to
voice-triggers:scripts/gen-skill-docs.tshasextractVoiceTriggers()/processVoiceTriggers().processVoiceTriggers()appendsVoice triggers (speech-to-text aliases): ...to the description.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:
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:
triggers:.voice-triggers:indescription:.Concrete example
qa/SKILL.md.tmplhas useful triggers like:Only the
voice-triggersvalues are appended to the description. The normaltriggersvalues are not.Expected behavior
Either:
triggers:phrases into the generateddescription:/ Codexopenai.yamlshort description, similar tovoice-triggers, ordescription:/voice-triggers:, ortriggers: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 forvoice-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 withtriggers:emits aUse when asked to...line in generated Codex/Claude descriptions, subject to the existing description length limit.