はじめに
AIコーディングエージェントのスキル(SKILL.md)機能を使っていますか?npx skills add(旧npx add-skill)を使えば、Claude Code・Cursor・Codex・OpenCodeなど35以上のエージェントにスキルをインストールできます。しかし自作スキルを追加したところなぜか1つだけ検出されないという問題に遭遇しました。
原因は SKILL.md の YAML frontmatter に潜む、たった1文字の罠でした。
この記事では、原因の特定プロセスと修正方法を紹介します。
対象読者: Claude Code / Cursor / Codex などのAIエージェント向けに自作スキルを作っている方、YAML frontmatter を扱う機会がある方
起きたこと
リポジトリに13個のスキルがあるのに、npx skills addを実行すると12個しか表示されない。
(npx add-skillは内部でnpx skills addにフォワードするだけなので、どちらを使っても結果は同じです)
Select skills to install (space to toggle)
│ ◻ calendar-sync
│ ◻ coderabbit-fix
│ ◻ commit
│ ◻ daily-breakdown
│ ◻ e2e-test
│ ◻ gtr-workflow
│ ◻ issue-workflow
│ ◻ pull-request ← memo-to-spec がない!
│ ◻ qiita-writer
│ ◻ tech-learning
│ ◻ weekly-breakdown
│ ◻ weekly-reconcile
追加したばかりの memo-to-spec だけが見えていません。
最初に疑ったこと(全部ハズレ)
| 仮説 | 検証結果 |
|---|---|
| プッシュが反映されていない? | GitHub APIで確認済み。反映されている |
| GitHubのAPIキャッシュ? |
git/trees APIで13個すべて見えている |
| npxのキャッシュ? |
clear-npx-cache しても変わらず |
| SKILL.mdのフォーマットが違う? | 他のスキルと同じ構造 |
| ディレクトリ構造の問題? |
references/サブディレクトリは他のスキルにもある |
原因の特定
npx skillsの内部実装を追って、スキル検出の流れを確認しました。
核心部分のコードはこれです:
async function parseSkillMd(skillMdPath, options) {
try {
const content = await readFile(skillMdPath, "utf-8");
const { data } = matter(content); // gray-matter でパース
if (!data.name || !data.description) return null; // ← ここで弾かれる
return { name: data.name, description: data.description, ... };
} catch {
return null; // パースエラーも静かに無視される
}
}
gray-matter(YAMLパーサー)がエラーを出し、catchで握りつぶされていたのです。
では何がパースエラーを起こしていたのか?
犯人:descriptionの中のコロン
問題の SKILL.md はこうでした:
---
name: memo-to-spec
description: 雑多なメモから仕様書を生成する。feature-spec-generatorとの違い: こちらは「既にあるメモ」を起点とする。
---
js-yaml でパースすると:
YAMLException: bad indentation of a mapping entry (3:237)
YAMLでは : (コロン+スペース)はキーとバリューの区切り文字です。
# YAMLパーサーの目にはこう見える
description: ...省略...違い: # ← ここで新しいキーが始まると解釈
こちらは「既にあるメモ」を... # ← バリューだがインデントがおかしい → エラー
つまり日本語の文章に自然に出てくる「〇〇の違い: 」がYAMLの構文と衝突していたのです。
修正方法
YAML のリテラルブロック(|)を使います:
# NG: コロンが構文として解釈される
description: 〇〇の違い: こちらは〜
# OK: リテラルブロックなら中身はプレーンテキスト
description: |
雑多なメモから仕様書を生成するスキル。
feature-spec-generatorとの違い: こちらは「既にあるメモ」を起点とする。
他にもクォートで囲む方法があります:
# OK: ダブルクォート
description: "〇〇の違い: こちらは〜"
# OK: シングルクォート
description: '〇〇の違い: こちらは〜'
YAMLで気をつけるべき文字まとめ
descriptionのような長い文字列を1行で書く場合、以下の文字に注意が必要です:
| 文字 | 問題 | 例 |
|---|---|---|
: (コロン+スペース) |
キーバリュー区切りと解釈 | 違い: こちらは |
# |
コメント開始と解釈 | C# の入門 |
{ }
|
フロースタイルマッピング | {name: value} |
[ ]
|
フロースタイルシーケンス | [1, 2, 3] |
@ ``` |
予約文字 |
対策: 長いdescriptionは | か > を使うのが安全
# | : 改行を維持
description: |
1行目
2行目
# > : 改行をスペースに変換(折りたたみ)
description: >
長い文章を
複数行で書ける
教訓
- YAMLの1行文字列に日本語を書くときはコロンに注意 — 「〇〇とは: 」「違い: 」など、日本語では自然な表現がYAML構文と衝突する
-
エラーを握りつぶすcatchは厄介 —
add-skillは親切にもエラーを出さずスキップするため、原因究明に時間がかかった -
長いdescriptionはリテラルブロック(
|)で書く — 特殊文字の心配がなくなり、可読性も上がる
まとめ
-
npx add-skill(npx skills add)でスキルが検出されない場合、SKILL.mdのYAML frontmatterのパースエラーを疑う - 日本語descriptionに含まれる
:がYAML構文と衝突するのが原因だった -
description: |のリテラルブロック形式を使えば安全 - 自作スキルを公開する際は
js-yamlやnpx js-yaml your-file.ymlでパースが通るか事前に確認するのがおすすめ