Claude CodeのSkillsが使われない・勝手に動く原因と対処|SKILL.mdの置き場所・frontmatterの書き間違い・descriptionの書き方とdisable-model-invocation

Claude CodeのSkillsが使われない・勝手に動く原因と対処

こんにちは、おうどんです🍜

新人さん「Claude Code のスキル、作ってみました! CSV の文字化けを直すやつです」

おうどん「おお、えらい。で、使ってくれた?」

新人さん「『CSV が文字化けしてる』って頼んだら、Claude さん、自分でゼロから直し始めました」

おうどん「……スキルは?」

新人さん「棚の上で、ほこりをかぶってます」

(会話は説明用の架空の場面です)

作ったのに、呼ばれない。
逆に、頼んでもいないのに、別のスキルが勝手に動く。

Claude Code のスキル(Skills)は、SKILL.md というファイル1枚で、Claude に「こういう仕事はこの手順でやってね」と教えられる便利な仕組みです。
ただ、仕組みを知らないまま書くと、ほこりをかぶるか、暴れるかのどちらかになりがちなんです。

うどん屋でたとえると、スキルはこういう作りになっています。

  • お品書き:壁に貼ってある、料理の名前と一行の説明。店員さん(Claude)は、いつもこれを見ている
  • レシピカード:引き出しにしまってある作り方。注文が入ってはじめて取り出す

店員(Claude)「壁のお品書きは毎日読んでます! 引き出しは、注文が来たら開けます!」

……つまり、お品書きに書いていないことは、店員さんの頭に入っていない。これが今日の話のほぼ全部です。

最初に、初心者向けのチェックリストです。

  • ファイルは .claude/skills/<スキル名>/SKILL.md。1行目は --- から始める
  • description には「何をするか」と「いつ使うか」を、先頭に書く
  • 勝手に動いてほしくないスキルには disable-model-invocation: true を付ける

この記事の仕様は、Claude Code 公式ドキュメントの Skills のページを 2026-10-15 に確認した内容です。Claude Code 自体の動きは今回の検証環境で実行して確かめていないので、設定例は「未実行」です。一方で、SKILL.md の書き間違いを探す Python スクリプトと、PowerShell の保存のしかたは、実際に実行して確かめています。

目次

スキルは「お品書き」と「レシピ」の2段で読まれる

スキルが呼ばれない理由の半分は、ここを知っているかどうかで決まります。
Claude が常に見ているのは、スキルの名前と説明だけ。中身は、使うと決めたときにはじめて読みます。

公式ドキュメントの Skills のページには、ふつうのセッションではスキルの説明はコンテキストに読み込まれ、スキルの中身全体は呼ばれたときにだけ読み込まれる、と書かれています。
コンテキスト(Claude がいま頭に置いている情報の全部)を節約するための作りです。

Agent Skills のベストプラクティスでも同じで、起動時に読まれるのは全スキルの name と description だけ、SKILL.md は関係ありそうになったときに読む、と説明されています。

新人さん「じゃあ、本文に『CSV の文字化けのときは私を使って』って、ていねいに書いておけば……」

おうどん「それ、引き出しの中のレシピカードに『私を注文して』って書いてるのと同じ」

……誰も開けない引き出しの中で、必死にアピールしているカード。切ない。

だから、呼ばれるかどうかを決めるのは description です。
呼び方は2通りあります。

呼び方誰が決める何を見て決める
Claude が自動で使うClaudeお品書き(description)と、頼まれた内容が合っているか
/スキル名 で呼ぶあなたなし。名前を打てば必ず読み込まれる

もう1つ、公式ページに地味に大事なことが書いてあります。
/スキル名 はメッセージの先頭に書いたときだけ直接動きます。「じゃあ /deploy でステージングに出して」のように文の途中に書くと、スキルを直接動かすのではなく、Claude に使ってよいという許可を出したことになります。

新人さん「文の途中に書いても、ちゃんと伝わってるんですね」

おうどん「伝わってはいる。でも『どうぞ』と言われた店員さんが、出すかどうかを考える」

……確実に出してほしいなら、注文は最初に言う。お店でも同じです。

まず確かめる3つ:置き場所・ファイル名・1行目

お品書きの書き方を見直す前に、そもそも壁に貼られているかを確かめます。
貼ってない品書きは、どんなに名文でも読まれません。

置き場所

公式ページの表を、よく使うものに絞って並べます。

種類置き場所使える範囲
個人用~/.claude/skills/<スキル名>/SKILL.mdそのパソコンのすべてのプロジェクト
プロジェクト用.claude/skills/<スキル名>/SKILL.mdそのリポジトリのセッション
入れ子<サブフォルダ>/.claude/skills/<スキル名>/SKILL.mdそのサブフォルダの中で作業するとき
プラグイン<プラグイン>/skills/<スキル名>/SKILL.mdプラグインを有効にした場所。/プラグイン名:スキル名 で呼ぶ

ポイントは3つです。

  • フォルダを1つ作って、その中に SKILL.md を置く。.claude/skills/SKILL.md のように直接置く形ではない
  • プロジェクト用は、起動したフォルダから親をたどってリポジトリのルートまで読まれる
  • 入れ子のスキルは、Claude がそのサブフォルダのファイルを初めて扱うときに読まれる

新人さん「.claude/skill/ に置いてました」

おうどん「s が1文字足りない。壁の、1枚となりに貼ってある」

……となりの店の壁に貼っても、うちの店員は見ません。

同じ名前がぶつかったら

同じ名前のスキルが2か所にあると、優先順位はエンタープライズ(組織の管理設定)→ 個人用 → プロジェクト用です。
公式ページの例では、deploy が ~/.claude/skills/ とプロジェクトの .claude/skills/ の両方にあると、/deploy で動くのは個人用です。

ここ、チーム開発でハマりやすいところです。
リポジトリのスキルを直したのに動きが変わらないときは、自分の個人用に同じ名前がないかを見てください。

また、スキルは同じ名前の .claude/commands/ のファイル(昔ながらのカスタムスラッシュコマンド)より優先されます。どちらも /deploy を作れて、動きは同じ、と書かれています。

ファイル名と1行目

公式ページによると、frontmatter(ファイル先頭の --- で挟んだ設定欄)として読まれるのは、最初の --- がファイルの1行目にあるときだけです。

---
name: csv-mojibake
description: CSVの文字化けを直す。CSVが文字化けした、読めない文字が出ると言われたときに使う
---

# CSV の文字化けを直す手順
...

1行目に見出しを書いて、2行目から --- を始めると、設定欄として読まれません。

店員「お品書きの上に、大きく『本日のおすすめ』と書いた紙が貼ってあって、その下は見ませんでした!」

おうどん「……せめて、ちらっと見て」

……ちらっとも見ないのが、仕様です。

確かめ方

公式ページのトラブルシューティングでは、次の順で確かめるよう書かれています(いずれも Claude Code の操作なので、今回は未実行です)。

  1. Claude に What skills are available?(使えるスキルは?)と聞いて、一覧に出るか見る
  2. /スキル名 で直接呼んでみる。ユーザーが呼べる設定のスキルなら、これで動く
  3. /スキル名 も通じないなら、名前に使えない文字が入っていないか見る(後の章で説明します)
  4. claude --debug で起動して、frontmatter の読み込みエラーがないか見る

既存の SKILL.md を書き換えた場合は、~/.claude/skills/・プロジェクトの .claude/skills/ などなら、再起動しなくてもそのセッション中に反映される、と書かれています。
ただし、起動したときに存在しなかったスキルのフォルダ(トップレベルの skills フォルダ)を新しく作ったときは、/reload-skills を実行します。

新人さん「直すたびに Claude Code を再起動してました。1日20回くらい」

おうどん「店ごと閉めて開け直してた。お品書きを1枚替えるために」

……常連さんが入口で困っています。

description は「何をするか」と「いつ使うか」を先頭に書く

お品書きが貼られているのに頼まれないなら、書いてある中身の問題です。
店員さんは、お客さんの注文と、お品書きの言葉を見比べて選びます。合う言葉がなければ、出しません。

公式ページの説明では、description は「スキルが何をするか、いつ使うか」で、Claude はこれを見て使うかどうかを決めます。
ベストプラクティスのページには、さらに具体的に書かれています。

  • 何をするかと、いつ使うか(きっかけになる場面)の両方を書く
  • 具体的に書き、キーワードを入れる
  • 三人称で書く。説明はシステムプロンプト(Claude への前提の指示)に差し込まれるので、「私は〜できます」「あなたは〜に使えます」のように視点がぶれると、見つけにくくなることがある

英語の例が中心なので、日本語ならこう書くと近いです。

# よくない例
description: いろいろ手伝う

# よい例
description: CSVの文字化けを直す。CSVが文字化けした、Excelで開くと読めない、cp932かUTF-8か分からないと言われたときに使う

新人さん「いろいろ手伝う、ダメですか。謙虚でいい感じかなって」

おうどん「お品書きに『いろいろ』って書いてあるお店で、それを頼む?」

新人さん「……頼みません。何が出てくるか分からないので」

……謙虚さは、お品書きでは不安にしか見えません。

それから、大事なことを先頭に書く。
公式ページによると、description と when_to_use(使う場面を書き足す項目)は合わせて 1,536 文字でお品書きから切られます。長い説明の最後に「Oracle の作業で使う」と書いても、切られた後ろ半分は Claude に届きません(この切り方は、あとの中級者向けの章で、スクリプトでも確かめます)。

description を書かなかった場合は、本文の最初の空でない行が代わりに使われる、と書かれています。

店員「説明がなかったので、レシピの1行目の『# 手順』を読み上げておきました!」

おうどん「……お品書きに『手順』とだけ書いてある店」

……何屋さんなのかすら、分かりません。

🔰 ここまで読めば今日から困らない

  • スキルは .claude/skills/<スキル名>/SKILL.md。個人用は ~/.claude/skills/ で、同じ名前ならこちらが勝つ
  • 1行目は ---。見出しを先に書かない
  • Claude が自動で使うかどうかは description しだい。何をするか+いつ使うかを、先頭に書く
  • 呼ばれないときは、まず /スキル名 で直接呼んでみる
  • 勝手に動いてほしくないなら disable-model-invocation: true(次の次の章)

ここから先は中級者向け。frontmatter の書き間違いを機械的に見つけるスクリプトと、勝手に動くスキルの止め方です。

frontmatter の書き間違い4つ:エラーが出ないのが怖い

ここからが本題の落とし穴です。
公式ページには、2つ気になることが書いてあります。

  • 知らない項目名は、エラーを出さずに無視される
  • YAML(frontmatter の書き方の決まり)として読めないと、スキルは読み込まれるが、項目は1つも設定されていない状態になる。/スキル名 では動くが、Claude は description で選べない

つまり、書き間違えても、怒られない。
ただ静かに、お品書きが白紙になるか、指示が無視されます。

店員「お品書きが読めなかったので、白紙のまま貼っておきました! 名前で呼ばれたら作ります!」

おうどん「……読めなかったって、言って」

……言わないのが、いちばん困る親切です。

ここで紹介する4つは、今回作ったチェックスクリプト(次の章)で実際に検出したものです。Claude Code が実際にどう扱うかは、公式ページに書かれている範囲で説明します。

1. 値に「: 」(コロン+スペース)がある

いちばんやりがちなのがこれです。

---
name: csv-mojibake
description: CSVの文字化けを直す。Use when: CSVが文字化けしたとき
---

Use when: の部分に、コロンとスペースが並んでいます。
YAML 1.2.2 の仕様の 7.3.3 節では、引用符でくくらない値(プレーンスカラー)は「: 」と「 #」の組み合わせを含んではならない、とされています。キーと値の区切りや、コメントと区別がつかなくなるからです。

直し方は、値を引用符でくくるか、YAML のブロック形式(> や |)にすることです。

description: "CSVの文字化けを直す。Use when: CSVが文字化けしたとき"
description: >
  CSVの文字化けを直す。
  Use when: CSVが文字化けしたとき

> は折りたたみ形式で、改行がスペースになります。| はリテラル形式で、改行がそのまま残ります(YAML 1.2.2 の 8.1 節)。
日本語の説明なら、そもそも半角コロンを使わず「使う場面:」のように全角で書くのも手です。

新人さん「コロン1個で、お品書きが白紙ですか」

おうどん「うどん屋で言うと、品書きの『:』が画びょうに見えて、全部はがされた感じ」

……画びょうは悪くない。貼り方が悪い。

2. 項目名を書き間違えている

---
name: release-notes
description: リリースノートを書く。リリースノート、変更履歴と言われたときに使う
disable_model_invocation: true
---

disable-model-invocation のハイフンを、アンダースコアにしています。
公式ページのとおりなら、知らない項目は無視されるので、このスキルはふつうに自動で呼ばれます。止めたつもりで、止まっていません。

新人さん「設定したのに勝手に動くんです! バグですか?」

おうどん「バグではなく、_ と - の違い」

……見た目がほぼ同じ2文字に、一日を持っていかれる。

3. 先頭に BOM が付いている

BOM(バイト・オーダー・マーク)は、ファイルの先頭に付けることがある「これは UTF-8 です」という目印の3バイト(EF BB BF)です。
Windows PowerShell 5.1 の Set-Content -Encoding UTF8 は、この BOM を付けて保存します。今回の検証環境で確かめました。

$text = "---`nname: bom-skill`ndescription: Formats weekly reports. Use when the user asks for a weekly report.`n---`nBody"

Set-Content -Path "$Dir\bom-skill\SKILL.md" -Value $text -Encoding UTF8
$b = [System.IO.File]::ReadAllBytes("$Dir\bom-skill\SKILL.md")
"Set-Content -Encoding UTF8 : " + (($b[0..4] | ForEach-Object { $_.ToString("X2") }) -join " ")

$noBom = New-Object System.Text.UTF8Encoding $false
[System.IO.File]::WriteAllText("$Dir\nobom-skill\SKILL.md", $text.Replace("bom-skill", "nobom-skill"), $noBom)
$b = [System.IO.File]::ReadAllBytes("$Dir\nobom-skill\SKILL.md")
"WriteAllText (UTF8, no BOM) : " + (($b[0..4] | ForEach-Object { $_.ToString("X2") }) -join " ")
PowerShell 5.1.19041.7725
Set-Content -Encoding UTF8 : EF BB BF 2D 2D
WriteAllText (UTF8, no BOM) : 2D 2D 2D 0A 6E

(今回の検証環境の Windows PowerShell 5.1 で実行した結果です。1行目はスクリプトの先頭で $PSVersionTable.PSVersion を出したもの。フォルダを作る行は省いています)

2D が - の文字です。BOM 付きのほうは、--- の前に3バイトが入っています。
見た目は --- でも、バイトで見ると1行目は --- ちょうどではありません。

ここは正直に書きます。Claude Code が BOM 付きの SKILL.md をどう扱うかは、公式ページに書かれておらず、今回は確かめていません。ちゃんと読めるかもしれません。
ただ、1行目が --- であることが条件なので、わざわざ危ない橋を渡る理由もありません。PowerShell 5.1 で作るなら、上の UTF8Encoding $false の書き方で BOM なしにしておくのが無難です。

新人さん「目に見えない3バイトのせいで、お品書きが読めないかもしれないんですか」

おうどん「かもしれない。でも、BOM なしで書けば、悩む時間がゼロになる」

……「かもしれない」に一日使うより、3バイト削るほうが早い。

4. 名前に見えない文字が入っている

コピー&ペーストで、ゼロ幅スペース(幅がゼロで画面に見えない空白、U+200B)が紛れ込むことがあります。

公式ページには、Claude Code が受け付けない名前として、ゼロ幅スペースや制御文字、私用領域の文字(アイコンフォントの記号など)、文字の向きを変える文字、普通の空白以外の空白(2つ続く空白や前後の空白を含む)、< と > が挙がっています。
/ のメニューにスキルが出ないときは、名前を疑ってください。

店員「お品書きに『fix-csv』と書いてあるのに、注文されても見つかりません!」

おうどん「字と字のあいだに、透明人間が立ってる」

……透明人間は、目では探せません。スクリプトで探します。

完成版:SKILL.md をまとめて点検するスクリプト

上の4つを目で探すのはつらいので、Python の標準ライブラリだけでチェックするスクリプトを書きました。
Claude Code の動きを再現するものではなく、公式ページに書かれた条件と、YAML のよくある書き間違いを機械的に探すだけのものです。frontmatter の読み方も、key: value と >・| のブロックだけを扱う簡易版です。

# skilllint.py : SKILL.md のよくある書き間違いを探す(標準ライブラリだけ)
# 使い方: python skilllint.py <skillsフォルダ> [<skillsフォルダ> ...]
#   先に書いたフォルダほど優先(個人用 → プロジェクト用 の順に渡す)
import re
import sys
import unicodedata
from pathlib import Path

# 公式ページ(code.claude.com/docs/en/skills)で確認した frontmatter の項目
KNOWN = {
    "name", "description", "when_to_use", "argument-hint", "arguments",
    "disable-model-invocation", "user-invocable", "allowed-tools",
    "disallowed-tools", "model", "effort", "context", "agent",
    "background", "hooks", "paths", "shell",
    "metadata", "license", "compatibility",
}
LIMIT = 1536  # description + when_to_use がお品書きで切られる文字数


def parse(path):
    """frontmatter を読む。ごく簡単な key: value と | > のブロックだけ扱う"""
    issues = []
    raw = path.read_bytes()
    if raw.startswith(b"\xef\xbb\xbf"):
        issues.append(("WARN", "先頭に BOM(EF BB BF)があります。1行目が --- ちょうどではなくなります"))
        raw = raw[3:]
    lines = raw.decode("utf-8").splitlines()
    if not lines or lines[0] != "---":
        issues.append(("ERROR", "1行目が --- ではありません。frontmatter として読まれません"))
        return {}, issues, lines
    if "---" not in lines[1:]:
        issues.append(("ERROR", "閉じの --- がありません"))
        return {}, issues, lines
    end = lines.index("---", 1)
    fm = {}
    i = 1
    while i < end:
        line = lines[i]
        m = re.match(r"^([A-Za-z0-9_-]+):(?:[ \t]+(.*))?$", line)
        if not m:
            if line.strip() and not line.startswith((" ", "\t", "#")):
                issues.append(("ERROR", f"{i + 1}行目を「キー: 値」として読めません: {line}"))
            i += 1
            continue
        key, val = m.group(1), (m.group(2) or "")
        if val in ("|", ">", "|-", ">-"):
            block = []
            i += 1
            while i < end and (lines[i].startswith((" ", "\t")) or not lines[i].strip()):
                if lines[i].strip():
                    block.append(lines[i].strip())
                i += 1
            fm[key] = ("\n" if val.startswith("|") else " ").join(block)
            continue
        if val[:1] in ('"', "'") and len(val) >= 2 and val[-1] == val[0]:
            val = val[1:-1]
        else:
            if ": " in val:
                issues.append(("ERROR", f"{key} の値に「: 」(コロン+スペース)があります。"
                                        "YAML の書き間違いになり、frontmatter が読めません"))
            if " #" in val:
                issues.append(("WARN", f"{key} の値に「 #」があります。そこから後ろはコメント扱いです"))
                val = val.split(" #")[0]
        fm[key] = val
        i += 1
    return fm, issues, lines[end + 1:]


def check_name(name):
    issues = []
    for ch in name:
        cat = unicodedata.category(ch)
        if cat in ("Cc", "Cf", "Co", "Cn"):
            issues.append(("ERROR", f"name に見えない文字 U+{ord(ch):04X}({cat})があります。"
                                    "Claude Code はこの名前を受け付けません"))
    if "<" in name or ">" in name:
        issues.append(("ERROR", "name に < か > があります。Claude Code はこの名前を受け付けません"))
    if name != name.strip() or "  " in name:
        issues.append(("ERROR", "name の前後か途中に余分な空白があります"))
    if not re.fullmatch(r"[a-z0-9-]{1,64}", name) or re.search("claude|anthropic", name):
        issues.append(("INFO", "API・claude.ai 向けの name の規則(小文字・数字・ハイフン、64文字以内、"
                               "claude と anthropic を含まない)には合いません"))
    return issues


def main(roots):
    seen = {}
    errors = 0
    for root in map(Path, roots):
        for d in sorted(p for p in root.iterdir() if p.is_dir()):
            files = {f.name: f for f in d.iterdir() if f.is_file()}
            label = f"{root.parent.parent.name}/{root.parent.name}/{root.name}/{d.name}"
            print(f"■ {label}")
            if "SKILL.md" not in files:
                near = [n for n in files if n.lower() == "skill.md"]
                if near:
                    print(f"  [WARN] ファイル名が {near[0]} です。公式の名前は SKILL.md(大文字)")
                else:
                    print("  [ERROR] SKILL.md がありません")
                    errors += 1
                print()
                continue
            fm, issues, body = parse(files["SKILL.md"])
            name = fm.get("name", d.name)
            if fm:
                for key in fm:
                    if key not in KNOWN:
                        hint = key.replace("_", "-")
                        tip = f"(もしかして {hint}?)" if hint in KNOWN else ""
                        issues.append(("WARN", f"知らない項目 {key} は、エラーにならずに無視されます{tip}"))
                issues += check_name(name)
            desc = fm.get("description", "")
            total = desc + fm.get("when_to_use", "")
            if not desc:
                first = next((l for l in body if l.strip()), "")
                issues.append(("INFO", f"description がありません。本文の最初の行「{first[:30]}」が代わりに使われます"))
            elif len(desc) < 20:
                issues.append(("WARN", f"description が {len(desc)} 文字です。いつ使うかの言葉が入っているか確認"))
            if len(total) > LIMIT:
                issues.append(("WARN", f"description + when_to_use が {len(total)} 文字。"
                                       f"{LIMIT} 文字で切られ、末尾「{total[LIMIT:][-12:]}」はお品書きに載りません"))
            if str(fm.get("disable-model-invocation", "")).lower() in ("true", "yes", "on", "1"):
                issues.append(("INFO", "Claude からは自動で呼ばれません(/名前 で呼んだときだけ動きます)"))
            if len(body) > 500:
                issues.append(("INFO", f"本文が {len(body)} 行あります。500 行以内が目安です"))
            if name in seen:
                issues.append(("WARN", f"/{name} は {seen[name]} にもあります。そちらが優先されます"))
            else:
                seen[name] = label
            for level, msg in issues or [("OK", "問題なし")]:
                print(f"  [{level}] {msg}")
                errors += level == "ERROR"
            print()
    return 1 if errors else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))

今回の検証用に、わざと書き間違えたサンプルのスキルを作りました。個人用を home/.claude/skills、プロジェクト用を proj/.claude/skills というフォルダに見立てて、この順で渡しています。

python skilllint.py demo/home/.claude/skills demo/proj/.claude/skills
■ home/.claude/skills/deploy
  [OK] 問題なし

■ proj/.claude/skills/bom-skill
  [WARN] 先頭に BOM(EF BB BF)があります。1行目が --- ちょうどではなくなります

■ proj/.claude/skills/csv-mojibake
  [ERROR] description の値に「: 」(コロン+スペース)があります。YAML の書き間違いになり、frontmatter が読めません

■ proj/.claude/skills/csv-mojibake-fixed
  [OK] 問題なし

■ proj/.claude/skills/deploy
  [WARN] /deploy は home/.claude/skills/deploy にもあります。そちらが優先されます

■ proj/.claude/skills/fix-csv
  [ERROR] name に見えない文字 U+200B(Cf)があります。Claude Code はこの名前を受け付けません
  [INFO] API・claude.ai 向けの name の規則(小文字・数字・ハイフン、64文字以内、claude と anthropic を含まない)には合いません

■ proj/.claude/skills/helper
  [WARN] description が 7 文字です。いつ使うかの言葉が入っているか確認

■ proj/.claude/skills/log-report
  [WARN] ファイル名が skill.md です。公式の名前は SKILL.md(大文字)

■ proj/.claude/skills/nobom-skill
  [OK] 問題なし

■ proj/.claude/skills/ops-manual
  [WARN] description + when_to_use が 1569 文字。1536 文字で切られ、末尾「Oracleの作業で使う」はお品書きに載りません

■ proj/.claude/skills/release-notes
  [WARN] 知らない項目 disable_model_invocation は、エラーにならずに無視されます(もしかして disable-model-invocation?)

■ proj/.claude/skills/weekly-report
  [ERROR] 1行目が --- ではありません。frontmatter として読まれません
  [INFO] description がありません。本文の最初の行「# 週報スキル」が代わりに使われます

(今回の検証環境の Python 3.13.1 で実行した結果です。実際はスクラッチ用フォルダの絶対パスを渡しています。ERROR が1つでもあると終了コードは 1、個人用のフォルダだけを渡して全部 OK のときは 0 でした)

サンプルの中身は、こうです。

サンプルわざと入れた間違い
bom-skillPowerShell 5.1 の Set-Content -Encoding UTF8 で保存(BOM 付き)
csv-mojibakedescription: CSVの文字化けを直す。Use when: CSVが文字化けしたとき
csv-mojibake-fixed上と同じ文を "..." でくくったもの
deploy(2つ)個人用とプロジェクト用に同じ名前
fix-csvname の途中にゼロ幅スペース
helperdescription: いろいろ手伝う
log-reportファイル名が skill.md(小文字)
nobom-skillbom-skill と同じ中身を、BOM なしの UTF-8 で保存したもの
ops-manual1,569 文字の説明で、「Oracleの作業で使う」が最後
release-notesdisable_model_invocation: true(アンダースコア)
weekly-report1行目が # 週報スキル で、2行目から ---

log-report の skill.md は、公式ページの表記が SKILL.md なので警告にしています。Claude Code が小文字の名前を読むかどうかは、今回は確かめていません。
weekly-report の INFO は、1行目が --- でないなら全体が本文になる、というこのスクリプトの見立てです。

新人さん「12個中、OK が3個……」

おうどん「わざと間違えたサンプルだから。わざとじゃなかったら、店じまいを考えるレベル」

……なお、OK の3個のうち2個は、間違いの直し方の見本です。

使い方のおすすめ:

  • スキルを書いたら、個人用とプロジェクト用のフォルダを、この順で渡して流す
  • 終了コードが 1 なら止まるので、Git のコミット前フック(pre-commit)や CI(自動テストの仕組み)にそのまま入れられる
  • 公式にも、frontmatter が読めない SKILL.md を探す claude plugin validate .claude/skills があります(Claude Code v2.1.233 以降。今回は未実行)。使える版なら、まずそちらを使いましょう

店員「チェックで ERROR が出たので、コミットを止めておきました!」

おうどん「……今回は、それで正解」

……止めてほしいときにだけ止まる。いちばんありがたい頑固さです。

勝手に動くスキルを止める:4つのレバー

今度は逆の悩みです。頼んでいないのに、スキルが動く。
店員さんが張り切って、注文していない大盛りを出してくるパターンです。

店員「お客さんが『デプロイ』って言ったので、本番に出しておきました!」

おうどん「今のは『デプロイって何?』っていう質問!」

……やる気がある店員ほど、止め方を知っておく必要があります。

公式ページのトラブルシューティングでは、説明をもっと具体的にするか、手動でだけ呼びたいなら disable-model-invocation: true を付ける、と書かれています。
ほかにも、止め方のレバーがいくつかあります(以下の設定例は、どれも未実行です)。

設定あなたが / で呼べるClaude が自動で使うお品書き(説明)は Claude に見える
何も書かないはいはいはい
disable-model-invocation: trueはいいいえいいえ
user-invocable: falseいいえ(/ メニューに出ない)はいはい

disable-model-invocation: true は、本番へのデプロイや、メールを送るような取り消しにくい操作のスキルに向いています。お品書きから消えるので、Claude は存在すら意識しません。

user-invocable: false は逆で、/ メニューには出さず、Claude だけが使う裏方のスキル向けです。

paths で、使う場面をファイルで絞る

paths にファイルのパターン(glob)を書くと、そのパターンに合うファイルを扱っているときだけ、Claude がスキルを自動で使うようになります。

---
name: sql-review
description: SQLの書き方をレビューする。SQLファイルの確認やレビューを頼まれたときに使う
paths: "**/*.sql"
---

(未実行の設定例です。パターンの書き方は、CLAUDE.md まわりの「パスごとのルール」と同じ形式だと公式ページに書かれています)

新人さん「SQL のスキルが、Python を書いているときにも出てきて困ってたんです」

おうどん「天ぷらの注文に、天かすの説明を始める店員さんだね」

……関係あるようで、ない。

自分で書いていないスキルは skillOverrides で

チームの共有リポジトリにあるスキルなど、SKILL.md を書き換えたくないときは、設定ファイルの skillOverrides で見え方を変えられます。

{
  "skillOverrides": {
    "helper": "name-only",
    "deploy": "user-invocable-only"
  }
}

(未実行の設定例です)

値は4種類です。

値Claude に見える/ メニュー
"on"名前と説明出る
"name-only"名前だけ出る
"user-invocable-only"見えない出る
"off"見えない出ない

書いていないスキルは "on" と同じ扱いです。
/skills メニューでスキルを選んでスペースキーを押すと状態が切り替わり、Esc で .claude/settings.local.json に保存される、とも書かれています。

新人さん「『off』にしたら、お品書きからも消えるんですね」

おうどん「消える。ただ、引き出しの中のレシピは残ってる」

……休業中の札を出しているだけで、レシピを捨てたわけではありません。

ここから先は上級者向け。読み飛ばしてもOKです。

お品書きには文字数の予算がある

スキルを増やしていくと、別の理由で呼ばれなくなることがあります。
お品書きの壁が、いっぱいになるんです。

公式ページの「Skill descriptions are cut short」の節には、こう書かれています。

  • お品書き(スキルの一覧)には、すべてのスキルの名前が必ず載る
  • ただし説明は、文字数の予算に収まらないと、一部が落とされる
  • 予算は、使っているモデルのコンテキストウィンドウ(一度に扱える情報量)の 1%
  • あふれたときは、呼ばれた回数が少ないスキルから説明が落とされる。よく使うスキルは説明が残る

つまり、たまにしか使わないスキルほど、説明が消えて、もっと呼ばれなくなる。

店員「人気のないメニューは、名前だけにしておきました! 壁がいっぱいなので!」

おうどん「それ、ますます頼まれなくなるやつ」

……合理的なのに、なぜか切ない。人気投票の最下位が、写真まで外される感じです。

確かめ方と、広げ方も書かれています(いずれも未実行)。

  • /doctor:お品書きのコンテキスト消費の見積もりと、大きい順の内訳
  • /context の Skills の行:予算を当てたあとのお品書きの大きさ(v2.1.196 より前は、全部の説明の合計で数えていて、実際より何倍も大きく見えることがあった)
  • /skill-doctor:スキルごとのコンテキストの重さと使われ方。使っていないスキルを探す
  • --debug:予算を超えたとき、デバッグログに警告が出る

予算を広げるなら、設定の skillListingBudgetFraction(0.02 で 2%)か、環境変数 SLASH_COMMAND_TOOL_CHAR_BUDGET(文字数を固定で指定)。
逆に、優先度の低いスキルを skillOverrides で "name-only" にして、ほかのスキルのために枠を空ける方法もあります。

なお、1つのスキルあたりの 1,536 文字の上限は、予算とは別にかかります。この上限は skillListingMaxDescChars で変えられる、と書かれています。

1,536 文字で切られると、最後に書いたキーワードが消えます。チェックスクリプトの ops-manual のサンプルで見たとおりです。

新人さん「じゃあ、予算を 10% にすれば全部載りますね!」

おうどん「壁一面がお品書きの店。お客さんの話を聞く場所がなくなる」

……お品書きもコンテキストを食べます。広げるより、まず減らす。

呼んだあとのスキル:読み直さない・圧縮で先頭だけ残る

最後に、呼ばれたあとの話です。
「最初はスキルどおりに動いたのに、途中から守らなくなった」という悩みも、公式ページのトラブルシューティングに載っています。

公式ページの「Skill content lifecycle」によると、

  • スキルが呼ばれると、SKILL.md の中身が1つのメッセージとして会話に入り、そのあとも残る
  • Claude Code は、あとのターンでスキルのファイルを読み直さない
  • allowed-tools(スキルを呼んだターンだけ、確認なしで使えるツール)の許可は、次のメッセージを送ったところで消える。残るのは指示だけ

そして、会話が長くなって自動の圧縮(コンパクション。古い会話を要約してコンテキストを空けること)が起きると、

  • 各スキルの直近の呼び出し1回分を、先頭の 5,000 トークンだけ、要約のあとに付け直す
  • 付け直すスキル全体の合計は 25,000 トークンまで
  • 最近呼んだスキルから順に詰めるので、1つのセッションでたくさんのスキルを呼ぶと、古いスキルは丸ごと落ちることがある

店員「レシピカードは最初に1回読みました! そのあと厨房が散らかったので、カードの上半分だけ残して片付けました!」

おうどん「下半分に『最後に味見』って書いてあったんだけど」

……片付け上手なのも、困りものです。

公式ページの対策は、こうです。

  • 毎回必ず守らせたいルールは、スキルではなく hooks(決まったタイミングで必ず動く仕組み)に移す。スキルの frontmatter の hooks に書けば、スキルを呼んでからセッションが終わるまで効く
  • 判断で使ってほしい指示は、一度きりの手順ではなく、作業全体に効く書き方にする(「テストを実行する」ではなく「編集のたびにテストを実行する」)
  • 圧縮のあとは、スキルをもう一度呼ぶ。そして大事な指示は SKILL.md の先頭近くに書く

hooks で止めるときの終了コードの落とし穴は、Claude Codeのhooksでブロックできない原因と対処にまとめました。
また、本文が長くなるなら、SKILL.md は 500 行以内を目安にして、詳しい資料は別ファイル(reference.md など)に分けて SKILL.md から参照する、と公式ページにもベストプラクティスにも書かれています。

新人さん「大事なことは先頭に。description と同じですね」

おうどん「お品書きも、レシピも、上から読まれる。うどんの湯切りと同じで、大事なのは最初の一振り」

……たとえが合っているかは、各自の湯切り経験にお任せします。

Claude Code と API・claude.ai で、frontmatter の規則が違う

同じ SKILL.md を、Claude Code と、claude.ai(ブラウザ版)や API の両方で使いたいときの注意です。

ベストプラクティスのページでは、name と description に次の決まりがあります。

項目決まり
name64 文字以内。小文字・数字・ハイフンだけ。XML タグなし。anthropic・claude を含まない
description空でない。1,024 文字以内。XML タグなし

新人さん「スキルの名前、『Claudeのお手伝い』にしようと思ってました」

おうどん「小文字・数字・ハイフンだけ、のルールに、大文字と日本語で二重にアウト」

……二重どころか、claude の文字まで入っています。

一方、Claude Code の公式ページで「受け付けない」と書かれているのは、先ほどの見えない文字や <・> などで、小文字だけ・64 文字以内という決まりは、今回確認した範囲には見当たりませんでした。

さらに大事なのが、項目名の扱いの違いです。Claude Code の公式ページには、

  • Claude Code:知らない項目は、エラーにせず無視する
  • claude.ai へのアップロードや Skills API:受け付ける項目は name、description、license、compatibility、metadata、allowed-tools だけ。それ以外があると、パッケージ化やアップロードがエラーで失敗する

と書かれています。
つまり、Claude Code 用に disable-model-invocation や paths を書いたスキルを、そのまま claude.ai にアップロードすると、はじかれます。

新人さん「Claude Code では何も言われなかったのに、claude.ai では門前払いでした」

おうどん「本店はゆるいけど、デパートの催事場は規則が厳しい。出店するなら、催事場の規則に合わせた品書きを用意する」

……同じうどんでも、出す場所で、のれんの書き方が変わります。

チェックスクリプトで INFO として出している「API・claude.ai 向けの name の規則」は、このためのものです。Claude Code だけで使うなら、気にしなくて大丈夫です。

🍜 まとめ:お品書きに書いたことだけが、店員さんに届く

冒頭の新人さんの CSV のスキル。原因の候補はこうでした。

  • 置き場所・ファイル名・1行目の --- が違って、壁に貼られていなかった
  • description が「いろいろ手伝う」で、注文の言葉と合わなかった
  • Use when: のコロンで YAML が壊れ、お品書きが白紙だった
  • 説明が長すぎて、大事なキーワードが 1,536 文字の外にあった

新人さん「……4つ全部、当てはまってました」

おうどん「フルコースだね」

……頼んでもいないのに、フルコース。

次は、こう言えるといいですよね。

新人さん「チェックスクリプト、全部 OK でした。description の先頭に『CSVの文字化けを直す』と書きました」

おうどん「じゃあ、頼んでみよう」

店員「CSV の文字化けですね! お品書きにありました。レシピカード、出します!」

……ほこりをかぶっていたスキルが、やっと厨房に立ちました。

スキルが呼ばれるかどうかは、レシピの出来ではなく、お品書きの一行で決まる。

まずは、手元のスキルの description を1つだけ開いて、「何をするか」と「いつ使うか」が先頭にあるか見てみてください。
書いてあったら、今日のまかないは、きつねうどんでどうぞ🍜

参考資料

  • Claude Code Docs:Extend Claude with skills — スキルの置き場所と優先順位、コマンド名の決まり方、frontmatter の項目(description・when_to_use・disable-model-invocation・user-invocable・allowed-tools・paths)、知らない項目は無視されること、YAML が読めないときの動き、1,536 文字の上限、/スキル名の呼び方、変更の反映と /reload-skills、skillOverrides、名前に使えない文字、トラブルシューティング(呼ばれない・呼ばれすぎ・守られない・説明が切られる、1% の予算、claude plugin validate)、スキルの中身の扱いと圧縮後の 5,000・25,000 トークン、claude.ai・API で受け付ける項目
  • Claude Docs:Skill authoring best practices — 起動時に読まれるのは name と description だけであること、description は何をするかといつ使うかを三人称で書くこと、name と description の規則(64 文字・1,024 文字など)、SKILL.md は 500 行以内
  • YAML Ain’t Markup Language (YAML) Version 1.2.2 — 引用符なしの値に「: 」と「 #」を含めないこと(7.3.3 節)、ブロック形式の > と |(8.1 節)

確認日:2026-10-15。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

おうどん|癒しと創作をたのしむ雑食クリエイター
写経アプリやクレイセラピー、優しい和風デザインがすき。
ZARDと刀剣と文字に癒されて、今はアプリ作ってます。

コメント

コメントする

目次