「Skill 便利すぎる、全部自動化できるじゃん」
Skill を 1 個書いた直後はだいたいこの気持ちです。開発フロー Skill を叩けば PR まで仕上がり、ブログ下書き Skill を叩けば草稿が出てくる。手が止まっていた作業が勝手に進むようになります。
3ヶ月後、Skill が 30 個を超えたあたりから雲行きが怪しくなります。意図しないタイミングで起動する Skill、関係ない Bash コマンドに発火する hook。permissions 確認待ちで止まる long-run Skill、nix run .#update で壊れる symlink。動くけど、触るのが怖い状態の出来上がりです。
実際に踏んだ 4 つのアンチパターンと直し方をまとめました。全部、私たちが踏みました。
前提:入門編・上級編の続編
「Skill を 1 個も書いたことがない」人向けではありません。5〜10 個運用し「あれ、最近壊れる頻度高くない?」と感じ始めた読者を想定しています。入門・設計は以下が前提です。
アンチパターン 1:description が曖昧で Skill が「やりすぎる」
description をそれっぽく書くと、書いた本人が想定していない地点まで Skill が走り切ってしまう。
実例:PR を勝手に merge する事件
開発フロー Skill にこんな不具合レポートが上がりました。
期待: レビューで LGTM を取得したら終了。実際: LGTM 後に PR を自動マージしてしまう。
原因は description 1 行でした。
description: |
End-to-end development flow automation - from issue to merged PR.
from issue to merged PR と書いたことで Claude は「責務は merged PR まで」と解釈しました。CI 通過直後に gh pr merge 相当の挙動を取ります。スクリプトに gh pr merge はどこにも書かれていないのに description だけで動いてしまう。
なぜ起きるか
LLM ベースの Skill で description は「取扱説明書」として機能します。書いた一文がそのままスコープになるため、コードに無くても書いてあれば LLM はやろうとします。
直し方:責務の境界線を書く
修正はほぼ description の言い換えだけでした。
description: |
End-to-end development flow automation - from issue to LGTM.
Note: Merge is performed manually by the user after review approval.
merged PR を LGTM にし、「merge は人間がやる」と明示。この 2 行で挙動が変わります。
frontmatter validation hook
description 以外にも必須フィールド欠落・文字数オーバーで詰まることがあります。validation スクリプトを PreToolUse hook として登録し、書き込み時点で機械的に弾きます。
- 必須フィールド(
name,description)の存在 - description の文字数上限(500 文字)
model/effort/contextの値バリデーション
frontmatter の effort 値は途中で low|medium|high|xhigh|max まで拡張されました(出典)。validate の許容値リストも公式仕様に合わせて更新する保守が発生します。忘れると新しい frontmatter が書けず全 Skill 編集が詰まります(実体験)。
アンチパターン 2:hook の matcher が雑で全 Bash に発火する
hook 設定でやらかすパターンです。
実例:全 Bash で発火していた問題
振り返りメモにはこうあります。
ifは hook オブジェクト内に配置する必要があり、誤配置で gh pr view 等の無関係なコマンドにもフックが発動していた。 内部ガード句も無く、ifが効かないケースで全 Bash コマンドに hook が走り確認ダイアログが頻発していた。
gh pr merge のときだけ走らせたかった hook があらゆる Bash コマンドに発火していたわけです。gh pr view でも git status でも確認ダイアログが出て、長時間走る Skill が止まります。
なぜ起きるか
matcher 設定(if フィールド)を信じすぎていたのが本質です。記述・配置ミスで簡単に効かなくなり、効かなくなっても気づきにくいのが厄介です。
直し方:内部ガード句を入れる
巨大インラインコマンドをスクリプト化し、先頭で gh pr merge 以外を早期 exit するガード句を足す二段構えにしました。
#!/usr/bin/env bash
set -euo pipefail
# 内部ガード句: matcher を信じない
case "${COMMAND:-}" in
"gh pr merge "*) ;;
*) exit 0 ;;
esac
# 以降、本来の処理
if だけに頼らず hook script 自身が発火条件を持つことで、matcher が壊れても壊れにくくします。新規 hook には tests/ にテストも添えます。prod credential 検知 hook に 25 件、Stop hook(未 commit 差分を残したまま終了を止める hook)に 9 件同梱済み。サイレントに壊れるのを防ぐ唯一の方法はテストです。
アンチパターン 3:permissions の deny が粗くて long-run が止まる
実例:feature push が毎回ブロックされていた
メモはこうです。
包括的な Bash(git push) deny を削除し、保護ブランチ(main/master/dev/develop)は個別ルールで維持。長時間走るスキルがパーミッション確認なしに feature push 可能に。
「危険そうな操作は全部 deny」で組み始めると安全に見えますが、長時間走る Skill がユーザー確認待ちで止まり続ける。夜間の巡回 Skill も同じ理由で詰まります。
なぜ起きるか
Bash(git push) の粒度では「main を禁止」と「feature も止める」を分離できません。文字列マッチ判定のため、安全性でなくコマンドの形で見るしかないのです。
直し方:PreToolUse hook に格上げ
PreToolUse hook で feature push を自動許可する構成に切り替えました。
Bash(git push)/Bash(git push origin)の包括 deny を削除- PreToolUse hook でブランチ判定(保護ブランチ→deny、feature/*→allow、判定不能→ask)
- refspec 指定の明示的保護 deny ルールは settings.json 側で維持
文字列マッチで判定できない部分は hook に逃がします。deny は最小集合に絞り、グレーゾーンは hook で動的判定します。
prod credential 検知 hook も同じ思想で permissionDecision = "ask" を返す設計です。誤検知の escape を残さないと長時間タスクが完全停止するため ask がデフォルトです。
Codex/Gemini との統一で詰まる
Codex (prefix_rule 形式) と統一しようとすると新しい罠がありました。Read(.env) 等のファイルアクセス制限や mv /* 等の glob パターンは prefix_rule で表現できません。Bash(git push) の bare 完全一致も全 push をブロックするため prompt に分類されます。permissions モデルは各ツールで方言があります。自動変換せず禁止ポリシーを言語化して Skill 側で各フォーマットに変換させるのが現実解でした(詳細はSkills 共有管理の記事)。
アンチパターン 4:symlink の管理機構を増やしすぎて自分で踏む
「Skill 設計」というより「運用ミス」ですが、3 ヶ月運用すると確実に踏みます。
実例:nix run .#update で skills symlink が壊れる
報告タイトルがそのまま症状です。
nix run .#updateを実行すると、~/.claude/skillsの symlink が切れる。
原因は単純で、~/.claude/skills を2 つのメカニズムで管理していました。
- home-manager の activation script
→
~/.claude/skillsを dotfiles 配下のclaude-code/skillsにリンク - setup スクリプト
→
~/.claude/skillsを 別リポジトリの skills 専用ディレクトリ にリンク
setup で (2) が効いて別リポジトリへのリンクができ、その後 nix run .#update で (1) が dotfiles 配下へのリンクに上書きします。dotfiles 配下は移動済みで存在しないため symlink が切れるわけです。
なぜ起きるか
「別管理に切り出したい」「dotfiles にも残したい」の両立で symlink の作成権限を持つ機構が複数並立します。単体では正しく動くため衝突に気づくのは壊れたあとです。
直し方:管理権限を 1 箇所に集約
シンプルに片方を諦める選択を取りました。
- home-manager の activation script から symlink 管理コードを削除(13 行)
- symlink の作成・更新は setup スクリプトに一元化
nix run .#update後も setup 側のリンクが維持される
「home-manager で全部管理したい」気持ちはわかりますが、lifecycle が異なる外部リンクは外したほうが壊れません。Single Source of Truth は思想ではなく、複数機構の事故を防ぐ運用ルールです。同じ発火源に claude-code/hooks/ → ~/.claude/hooks/ の自動 symlink も追加しました。hook 追加時の手動 symlink 忘れも防いでいます。
まとめ
4 つを並べて気づくのは、症状はバラバラだけど原因が同じ構造だということです。
| アンチパターン | 表面的な症状 | 根本原因 |
|---|---|---|
| description が曖昧 | Skill が想定外まで走る | LLM が description を完了条件として読む |
| hook matcher が雑 | 全 Bash に発火する | matcher を信じすぎ・script 側の内部ガード欠落 |
| permissions が粗い | long-run が止まる | 文字列マッチ deny で粒度を分離できない |
| symlink 管理の重複 | nix update で壊れる | 同じリソースを複数の機構で管理 |
共通しているのは「1 箇所だけで安全性を担保しようとしている」という構造です。description 1 行、matcher 1 個、deny 1 行、activation 1 個。書いた瞬間は綺麗でも、運用が長くなるほど他の機構と衝突したり抜け道が露呈したりします。
回避策のパターンはほぼ同じ形です。
- 二段構えにする:matcher だけでなく hook 本体にもガード句、deny だけでなく PreToolUse hook で動的判定
- 責務を文章で書く:description に「やらないこと」を明示
- テストを添える:hook には tests/、frontmatter には validation
- 管理機構を 1 つに絞る:symlink の作成権限をどこか 1 箇所に統一
Skill を書くのは楽しいですが、30 個を超えてもメンテできるかは書いたあとの設計判断にかかっているというのが 3 ヶ月運用しての感想でした。増やす前に description / hook matcher / deny / symlink を見直してみてください。たぶん何か見つかります。



