generic harness: the router description's `: ` makes SKILL.md invalid YAML, so strict loaders (Pi) drop the skill

Open
#4,163 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@NekoCCN is already working on this.

Since Nov 8, 2026.

  • #4165 by @NekoCCN — open

Assessment

Difficulty
1/5
Estimated time
1-3 hours
Newbie friendliness
24/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Stale
Tech stack
typescript, yaml

Research direction

Read lib/harness-register.ts and scripts/gen-skill-docs.ts to compare how their frontmatter writers encode descriptions, then inspect the linked PR #4165 for work already underway. The issue identifies routerSkill() and flatFrontmatter() as the affected entry points; done means both emitted descriptions parse as YAML while preserving their original text.

Written by the indexing model from the issue text.

Description

What happened

gstack-harness register generic (the "any other harness" target, docs/hosts.md) writes a SKILL.md whose frontmatter is not valid YAML: the router description contains an interior ": " (... Load first for any coding task: lists every gstack skill ...) and is emitted as a plain scalar, so a strict YAML loader reads it as a nested mapping and rejects the file.

A harness that parses frontmatter with a real YAML parser then drops the skill silently, so register reports success and the skill never appears:

gstack-harness: any other harness: 60 gstack skills reachable through one router skill at <dir> (installed, 1 files written)

…and in Pi, ~/.agents/skills/gstack/SKILL.md is never listed, with no error the user can act on (Pi documents malformed SKILL.md files as "not loaded").

This is the same class of bug as #1778 (generated SKILL.md descriptions with colons are invalid YAML), which was fixed in scripts/gen-skill-docs.ts by quoting with toYamlInlineScalar. That helper stayed local to the generator, and the two frontmatter writers in lib/harness-register.ts never got it:

  • routerSkill() → 'description: ' + ROUTER_DESCRIPTION (router mode, the failing one)
  • flatFrontmatter() → 'description: ' + desc (--skills all for Claude-render targets: any skill description with a colon breaks the same way)

Steps to reproduce

git clone https://github.com/garrytan/gstack && cd gstack
bun install
./bin/gstack-harness register generic --skills-dir /tmp/repro-skills
head -3 /tmp/repro-skills/gstack/SKILL.md

Then parse the frontmatter with a strict YAML loader (any: yaml, js-yaml, PyYAML, or the loader the target harness uses):

node -e 'const fs=require("fs"),{parse}=require("yaml");
const m=/^---\r?\n([\s\S]*?)\r?\n---/m.exec(fs.readFileSync(process.argv[1],"utf8"));
try{const o=parse(m[1]);console.log("OK name="+o.name)}catch(e){console.log("REJECTED: "+e.message.split("\n")[0])}' \
  /tmp/repro-skills/gstack/SKILL.md

Real output

Before (current main, base commit for this report 6042f6a, VERSION 1.98.0.0):

REJECTED: Nested mappings are not allowed in compact mappings at line 2, column 14:

description: Use to review a diff or PR, debug a bug or failing test, QA test a…
             ^

After quoting the same value with toYamlInlineScalar (PR linked below):

OK name=gstack descLen=229

The description parses back to exactly ROUTER_DESCRIPTION, so nothing is lost or rewritten — only the encoding changes.

Expected

Frontmatter written by gstack-harness register parses as YAML for every target, including harnesses that use a strict loader (Pi, QM), and the register output is a skill the harness actually lists.

Impact

--skills-dir registration with generic produces a skill that YAML-strict harnesses never load. Pi is the case I hit; ~/.agents/skills is Pi's documented skill location, so the natural install path is exactly this one. --context / --print are unaffected (no frontmatter), and the Claude/Codex/Cursor/Gemini routers are unaffected (their committed description has no colon).

Interim workaround: after register, quote the value by hand (description: "<text>"), or register a --context block instead. Both are overwritten by the next register / --refresh-registered.

Readiness report

doctor (paths shortened to ~; 2 warns and 1 not-configured are unrelated to this issue)
gstack doctor 1.98.0.0
ok              install root    ~/.claude/skills/gstack: opencode global current (1.98.0.0), cursor global current (1.98.0.0), agy global current (1.98.0.0), gemini global current (1.98.0.0), codex global current (1.98.0.0), claude global current (1.98.0.0)
ok              rescue refs     none in ~/.claude/skills/gstack
ok              state root      ~/.gstack durable=yes (selected by default)
ok              bun             1.4.2 at ~/.bun/bin/bun (floor 1.3.3, tested 1.4.2)
ok              hooks           15 hook(s) parse
ok              windows bash    C:/Program Files/Git/bin/bash.exe (chosen by git --exec-path)
ok              codex           codex-cli 0.166.0; self-locate ok
warn            codex probe     not probed; Codex readiness is unverified until a skill or --live runs it
not configured  artifacts sync  off; cross-machine artifacts sync is unavailable
ok              browse bundle   browse/dist built
ok              binaries        find-browse, design, pdf, gstack-global-discover launch (4 of 4)
ok              cso             native helper 1.98.0.0
ok              claude code     2.1.319 (Claude Code)
ok              session journal no session journals for this project
ok              guard log       no guard decisions logged
13 ok, 2 warn, 1 not configured, 0 fail

Environment

  • Host: generic target (Pi 0.x, skills root ~/.agents/skills), also affects any harness with a strict YAML frontmatter loader
  • OS: Windows 11, Git for Windows bash, Bun 1.4.2, Node v26.12.1
  • gstack: VERSION 1.98.0.0 installed; reproduced on main at 6042f6ad48774504f918ab1391908c1ccdb11fe0

Fix: PR to follow (extracts toYamlInlineScalar into lib/yaml-scalar.ts, uses it at both harness-register.ts emission sites, with tests).

Dominant language
TypeScript
Stars
139k
Forks
20.6k
Avg merge
10h 12m
Merged PRs (30d)
655

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from garrytan/gstack

All issues in garrytan/gstack

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.