1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SKILL.mdのdescriptionにコロンを書いたら検出されなくなった話

1
Posted at

はじめに

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: >
  長い文章を
  複数行で書ける

教訓

  1. YAMLの1行文字列に日本語を書くときはコロンに注意 — 「〇〇とは: 」「違い: 」など、日本語では自然な表現がYAML構文と衝突する
  2. エラーを握りつぶすcatchは厄介add-skillは親切にもエラーを出さずスキップするため、原因究明に時間がかかった
  3. 長いdescriptionはリテラルブロック(|)で書く — 特殊文字の心配がなくなり、可読性も上がる

まとめ

  • npx add-skillnpx skills add)でスキルが検出されない場合、SKILL.mdのYAML frontmatterのパースエラーを疑う
  • 日本語descriptionに含まれる: がYAML構文と衝突するのが原因だった
  • description: | のリテラルブロック形式を使えば安全
  • 自作スキルを公開する際は js-yamlnpx js-yaml your-file.yml でパースが通るか事前に確認するのがおすすめ

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?