カスタムスラッシュコマンドを作りはじめると、たいてい同じところで止まります。手順は書けたけれど、対象を毎回変えたい。ファイル名や課題番号を実行時に渡したい。

Claude Codeにはそのための書き方が何段階か用意されています。単純なものから順に手を動かしていきます。コマンドをまだ作ったことがない場合は、先に作り方を見ておくと迷いません。

まず全部まとめて受け取る

いちばん簡単なのが $ARGUMENTS です。本文にこれを書いておくと、コマンド名のあとに打った文字列がそのまま入ります。

---
description: GitHubのイシュー(登録された課題)を直す
disable-model-invocation: true
---

GitHubのイシュー $ARGUMENTS を、このリポジトリの書き方に合わせて直してください。

1. イシューの内容を読む
2. 直す範囲を決める
3. 実装する
4. テストを書く

このファイルを .claude/skills/fix-issue/SKILL.md として保存すると、/fix-issue が使えるようになります。GitHub のイシュー番号を付けて /fix-issue 123 と打つと、本文の $ARGUMENTS123 に置き換わってからClaudeに渡ります。

事故が減るのが、$ARGUMENTS を書き忘れた場合の挙動です。引数を付けて呼んだのに置き場所がないとき、Claude Codeは本文の末尾に ARGUMENTS: 打った内容 という行を足します。無視されず、文脈の最後にぶら下がる。動きがおかしいときはここを疑うと早いです。

1つずつ、位置で取り出す

渡したい値が複数あるなら、位置を指定して取り出せます。$ARGUMENTS[0] が1つめ、$ARGUMENTS[1] が2つめ。短く $0 $1 と書いても同じです。

$0 のコンポーネントを $1 から $2 へ移してください。
既存の動きとテストは変えないこと。

/migrate-component SearchBar React Vue と打てば、順に SearchBar React Vue が入ります。

区切りはターミナルでコマンドを打つときと同じ考え方で、空白を含む値は引用符でくくります。 /my-skill "hello world" second なら $0hello world になります。

渡さなかった番号の扱いは仕様が分かれています。$2 のような位置指定は置き換わらず文字として残り、後述する名前付きだと空文字になります。指定漏れに気づきたいなら位置指定のほうが目立ちます。

金額の $1.00 のように、数字の前のドルを記号ではなくただの文字として扱わせたい場合は、\$1.00 とバックスラッシュを前に置きます。

名前を付けて読みやすくする

位置指定は書くのは楽ですが、後から読むと $1 が何なのか分かりません。フロントマター(ファイル先頭の --- で挟んだ設定欄)で名前を宣言すると、本文を日本語のように読めます。

---
description: イシューを指定のブランチで直す
arguments: [issue, branch]
argument-hint: [issue-number] [branch-name]
---

イシュー $issue を $branch ブランチ(作業を枝分かれさせておく単位)で直してください。

arguments に並べた名前が、渡された順に対応します。argument-hint を書くと、/ を打って候補が出るときに何を渡すのかが表示されます。自分以外も使うなら、これだけで問い合わせが減ります。

実行時の状況を、その場で埋め込む

引数は人が打つ値です。別に、実行した瞬間の状態を自動で入れる書き方もあります。バッククォート(` の記号)の前に ! を付ける記法です。

## いまの変更

!`git diff HEAD`

## お願い

上の差分を3行で要約して、危なそうなところを挙げてください。

git diff HEAD は、まだ確定していない変更点を出すコマンドです。この行はClaudeが読む前に実行され、出力がその場所に差し込まれます。Claudeは「差分が既に貼られた指示書」を受け取る形です。Claudeが実行するのではなく、渡す前の下ごしらえだと考えると分かりやすいです。

複数行なら ```! で開くコードブロックを使います。

注意点が2つあります。! は行頭か空白の直後でないと記法として認識されず、KEY=!`cmd` のように前に文字が付くとただの文字列になります。もう1つ、差し込まれた出力は再走査されないので、出力が別のコマンドを呼ぶ入れ子はできません。

設定で disableSkillShellExecutiontrue にすると実行そのものを止められます。組織で禁じる場合の扱いは別記事に書きました。

セッション番号や置き場所も使える

引数のほかに、決まった名前で使える値もあります。よく使うのは3つ。

${CLAUDE_SESSION_ID} は今のセッションを識別するIDで、ログの名前を分けたいときに便利です。${CLAUDE_SKILL_DIR} はそのコマンドが置かれているディレクトリを指し、同梱したスクリプトを呼ぶときに使います。${CLAUDE_PROJECT_DIR} はリポジトリのいちばん上のフォルダです(こちらは Claude Code v2.1.196 以降)。

相対パスで書くと、どこから呼ぶかで動いたり動かなかったりします。置き場所を指す変数に変えると揺れが消えます。

積み重ねて呼ぶこともできる

1つのメッセージの先頭に、コマンドを複数並べて呼べます。/write-tests /fix-issue 123 と打つと両方が読み込まれ、末尾の 123それぞれの引数として渡ります。

最初の1つに加えて5つまで展開されますが、スキルではないただの文字列に当たった時点で止まります。 別のClaudeを裏で立ち上げて任せるタイプ(サブエージェント。/code-review など)や、引数自体がスラッシュで始まりうるもの(/loop など)も同じく打ち切りです。2つか3つに留めるほうが読み通せます(積み上げは v2.1.199 以降)。

カスタムコマンドとスキルの関係は別記事で整理しました。

渡し方を決めてから中身を書く

先に「何を渡すコマンドなのか」を決めてしまうと迷いません。1つだけなら $ARGUMENTS、2つ以上で後から読み返すなら名前付き、人が打たない値なら ! の差し込み。

私は最初、全部を $ARGUMENTS で受けて本文の中で場合分けしていました。読み返すと何を渡すのか分からなくなり、結局 argument-hint を足すことに。渡すものが決まっているなら、最初から宣言しておくほうが後の自分に親切です。