fix(cli): quote the skill description so strict YAML readers accept it (#1554)

The installed SKILL.md carried an unquoted `description` whose value contains
"Triggers on: ". In YAML a colon-space inside an unquoted scalar is a
nested-mapping indicator, so strict readers — js-yaml's `load`, the frontmatter
parser in `npx skills` — reject the whole document. The skill does not load
wrongly; it does not load at all, and nothing in the install output says why.

Quoting the scalar fixes it without touching a word of the text.

The test checks the RULE rather than this string: for every skill, any
frontmatter value containing ": " must be quoted. Pinning the current wording
would let the next skill someone adds reintroduce exactly this bug. Verified by
reverting the fix — the test goes red, and green again with it restored.

Reported by @xezzon.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel
2026-08-12 13:11:56 +02:00
parent 42e320a6cb
commit 2bb94372a4
2 changed files with 61 additions and 2 deletions
+7 -2
View File
@@ -1255,12 +1255,17 @@ int cbm_replace_binary(const char *path, const unsigned char *data, int len, int
static const char skill_content[] =
"---\n"
"name: codebase-memory\n"
"description: Use the codebase knowledge graph for structural code queries. "
/* #1554: the value contains "Triggers on: " — a colon-space, which YAML
* reads as a nested-mapping indicator inside an unquoted scalar. Strict
* parsers (js-yaml's load, the frontmatter reader in `npx skills`) reject
* the whole file, so the skill silently fails to load rather than loading
* wrongly. Quoting the scalar is the fix; the text itself is unchanged. */
"description: \"Use the codebase knowledge graph for structural code queries. "
"Triggers on: explore the codebase, understand the architecture, what functions exist, "
"show me the structure, who calls this function, what does X call, trace the call chain, "
"find callers of, show dependencies, impact analysis, dead code, unused functions, "
"high fan-out, refactor candidates, code quality audit, graph query syntax, "
"Cypher query examples, edge types, how to use search_graph.\n"
"Cypher query examples, edge types, how to use search_graph.\"\n"
"---\n"
"\n"
"# Codebase Memory — Knowledge Graph Tools\n"
+54
View File
@@ -12101,6 +12101,59 @@ TEST(cli_sha256_file_matches_known_vector) {
* words people already type is not. Both flags must be accepted, do nothing,
* and say so once. A genuinely unknown flag must still be rejected, or this
* degenerates into ignoring typos. */
/* #1554: the skill's `description` contained "Triggers on: " — a colon-space,
* which YAML reads as a nested-mapping indicator inside an UNQUOTED scalar.
* Strict readers (js-yaml's load, the frontmatter parser in `npx skills`)
* reject the whole document, so the installed skill silently fails to load.
*
* The rule is checked for EVERY skill, not just the one that broke: any
* frontmatter scalar whose value contains ": " must be quoted. Pinning only
* the current text would let the next added skill reintroduce it. */
TEST(cli_skill_frontmatter_scalars_with_colons_are_quoted_issue1554) {
const cbm_skill_t *sk = cbm_get_skills();
ASSERT_NOT_NULL(sk);
for (int i = 0; i < CBM_SKILL_COUNT; i++) {
const char *body = sk[i].content;
ASSERT_NOT_NULL(body);
/* Frontmatter is the block between the first two "---" lines. */
ASSERT_TRUE(strncmp(body, "---\n", 4) == 0);
const char *end = strstr(body + 4, "\n---\n");
ASSERT_NOT_NULL(end);
const char *line = body + 4;
while (line < end) {
const char *eol = strchr(line, '\n');
if (!eol || eol > end) {
break;
}
const char *sep = NULL;
for (const char *c = line; c < eol - 1; c++) {
if (c[0] == ':' && c[1] == ' ') {
sep = c;
break;
}
}
if (sep) {
const char *value = sep + 2;
/* A value that itself contains ": " must be quoted, or a
* strict parser treats it as a nested mapping. */
bool has_inner_colon = false;
for (const char *c = value; c < eol - 1; c++) {
if (c[0] == ':' && c[1] == ' ') {
has_inner_colon = true;
break;
}
}
if (has_inner_colon) {
ASSERT_TRUE(value[0] == '"' || value[0] == '\'');
}
}
line = eol + 1;
}
}
PASS();
}
TEST(cli_update_accepts_retired_variant_flags_issue1544) {
char *ui_argv[] = {"--dry-run", "--ui", "--yes"};
char *standard_argv[] = {"--dry-run", "--standard", "--yes"};
@@ -12189,6 +12242,7 @@ SUITE(cli) {
RUN_TEST(cli_install_config_failure_keeps_published_binary);
RUN_TEST(cli_update_download_failure_does_not_quiesce_sessions);
RUN_TEST(cli_update_already_current_does_not_quiesce_sessions);
RUN_TEST(cli_skill_frontmatter_scalars_with_colons_are_quoted_issue1554);
RUN_TEST(cli_update_accepts_retired_variant_flags_issue1544);
RUN_TEST(cli_update_agent_configs_finish_before_guard_release);
RUN_TEST(cli_uninstall_quiesces_active_cohort_before_removing_binary_and_index);