Agent Skillsにスクリプトを同梱する設計 — エラー処理と実行・参照の書き分け
Skillにスクリプトを入れるなら、エラーはスクリプト内で処理し、SKILL.mdで「実行」か「参照」かを書き分けます。定数の根拠と検証つき中間ファイルまで、設計の要点を例つきで解説します。
Agent Skillsにスクリプトを同梱するときの設計判断は、4つに絞れます。エラーをスクリプトの中で処理すること、SKILL.mdで「実行する」か「参照する」かを書き分けること、定数に根拠を残すこと、実行前に検証できる中間ファイルを挟むことです。ベストプラクティスの「Advanced: Skills with executable code」節がこの4点を扱っており、本記事はそれぞれを例つきで掘り下げます。
命名・description・本文の分割はAgent Skillsの命名規則と段階的開示の設計原則にあります。ここでは、Markdownだけで済まないSkill、つまりスクリプトを抱えるSkillに話を絞ります。
スクリプトを同梱すると何が変わるか
Markdownだけのスキルは、Claudeが文章を読んで自分でコードを書き、実行します。スクリプトを同梱すると、その部分は書かれたコードをそのまま走らせる形に変わります。
同梱の利点は4つあります。
- 生成したコードより信頼できる
- コードをコンテキストに載せずに済み、トークンを節約できる
- コード生成の時間が要らない
- 使うたびに挙動がそろう
Claudeがスクリプトを書けるとしても、あらかじめ用意しておく価値があるという立場です。
仕組みの面では、実行環境にファイルシステムとbashがあることが前提になります。Skillのnameとdescriptionは起動時にシステムプロンプトへ読み込まれ、SKILL.mdや他のファイルは必要になったときに読まれます。スクリプトはbash経由で走り、中身を読み込まずに済むため、コンテキストを消費するのは出力だけです。
Claude Codeのドキュメントも同じ構成を示しています。scripts/helper.pyには「utility script - executed, not loaded」と注記が付いており、実行はされても、中身はコンテキストへ読み込まれません。
どこまでスクリプトに固定するか — 自由度の3段階
スクリプトにするかどうかは、作業の壊れやすさとばらつきで決まります。指示の具体性は作業に合わせて変えます。自由度は3段階です。
| 自由度 | 向く場面 | SKILL.mdの書き方 |
|---|---|---|
| 高 | 向く場面複数の方法が成り立つ、文脈で判断が変わる | SKILL.mdの書き方文章の手順だけを書く(コードレビューの観点など) |
| 中 | 向く場面望ましい型はあるが、多少の変化は許せる | SKILL.mdの書き方パラメータ付きの擬似コードやスクリプトの雛形を置く |
| 低 | 向く場面壊れやすい、一貫性が最優先、順序が決まっている | SKILL.mdの書き方実行するコマンドを一字一句指定する |
低自由度の例はデータベース移行です。python scripts/migrate.py --verify --backupを「そのまま実行し、コマンドを変えたりフラグを足したりしない」と書きます。
たとえるなら、両側が崖の細い橋では、安全な道が1本しかないので、手すりと正確な指示が要ります。何も危険のない野原なら、方向だけ示して任せられます。同梱スクリプトは、この橋の部分を固める道具です。何でもスクリプトにすると、判断が要る箇所まで硬直します。
モデルによっても、必要な具体性は変わります。Haikuでは指示が足りているか、Sonnetでは明快で無駄がないか、Opusでは説明しすぎていないかを見る、という観点があります。スクリプトに固めた部分は出力のぶれが小さくなるはずなので、小さいモデルで動かすSkillほど、橋の部分を固める価値が高いという推論が成り立ちます。これはドキュメントに書かれた効果ではないため、実際のモデルで試して確かめてください。
エラーは丸投げせず、スクリプトの中で処理する
「Solve, don't defer」は、エラー条件をClaudeに任せず、スクリプトが自分で扱うという原則です。
悪い例は、ファイルを開いて失敗したら落ちるだけのコードです。例外が出ればClaudeは原因を推測して作り直すことになり、同梱した意味が薄れます。良い例は、ファイルが無ければ既定の内容で作成し、権限が無ければ代替を使います。ベストプラクティスの例を、構造そのままに抜き出します。
def process_file(path):
"""Process a file, creating it if it doesn't exist."""
try:
with open(path) as f:
return f.read()
except FileNotFoundError:
# Create file with default content instead of failing
print(f"File {path} not found, creating default")
with open(path, "w") as f:
f.write("")
return ""
except PermissionError:
# Provide alternative instead of failing
print(f"Cannot access {path}, using default")
return ""押さえたいのは2点です。
1つ目は、処理した事実をprintで出力に残している点です。出力はClaudeの目に入る唯一の情報なので、黙って既定値に切り替えると、あとで結果の違和感を追えなくなります。
2つ目は、何でも握りつぶす書き方とは違う点です。この例が処理するのは、既定値で続けて問題ない2種類の失敗だけです。続行すると結果が壊れるケースは、原因が分かるメッセージを添えて失敗させるほうが筋に合います。この線引きは文面の例から読み取れる設計であり、ドキュメントが明示している規則ではありません。
定数には根拠を書く — voodoo constantsを避ける
同じ節に、設定値の書き方があります。理由の分からない定数は「voodoo constants」と呼ばれ、Ousterhoutの法則が引き合いに出されています。注意書きは「正しい値を自分が知らないなら、Claudeにどう決められるのか」という問いです。
# HTTP requests typically complete within 30 seconds
# Longer timeout accounts for slow connections
REQUEST_TIMEOUT = 30
# Three retries balances reliability versus speed
# Most intermittent failures resolve by the second retry
MAX_RETRIES = 3悪い例は、TIMEOUT = 47やRETRIES = 5に理由のコメントが無いものです。47秒の根拠が書かれていなければ、Claudeは状況に合わせて変えてよいのか判断できません。
根拠を書くと、値を変える条件が決まります。「ほとんどの一時的な失敗は2回目の再試行までに解消する」と書いてあれば、再試行を増やす前に、失敗の原因を疑う理由ができます。
「実行」か「参照」かをSKILL.mdで書き分ける
ここは重要な区別です。同じスクリプトでも、Claudeに実行させる場合と、読んで理解させる場合があります。
スクリプトの扱いを文面で指定する
実行させる
「analyze_form.pyを実行してフィールドを抽出する」と書きます。Claudeはbashで走らせ、出力だけをコンテキストに取り込みます。
参照させる
「フィールド抽出のアルゴリズムはanalyze_form.pyを参照」と書きます。Claudeはファイルを読み、中身を理解したうえで使います。
ほとんどの補助スクリプトは、実行させるほうが確実で効率もよくなります。読ませる場合はファイル全体がコンテキストに入るため、トークンを払うのは「読ませる価値のあるロジック」に限る、と考えると判断しやすくなります。
SKILL.mdの書き方の例です。次は簡略にした形で、スクリプトごとに用途・コマンド・結果を並べます。
## Utility scripts
**analyze_form.py**: Extract all form fields from PDF
実行: python scripts/analyze_form.py input.pdf > fields.json
**validate_boxes.py**: Check for overlapping bounding boxes
実行: python scripts/validate_boxes.py fields.json
結果: "OK" か、衝突の一覧出力形式まで書いておくと、Claudeは次のスクリプトへ何を渡せばよいかが分かります。逆に動詞が曖昧な文面(「analyze_form.pyを使う」など)では、実行と参照のどちらなのかが決まりません。
Claude Codeでは、実行を許可の仕組みと対にする
Claude Codeには、同梱スクリプトを実行する側の仕組みがあります。frontmatterのallowed-toolsと、スキルの置き場所を指す${CLAUDE_SKILL_DIR}の組み合わせです。
---
name: render-chart
description: Render a chart from a CSV file
allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/render.sh *)
---
Run `${CLAUDE_SKILL_DIR}/scripts/render.sh <csv-file>` to render the chart.${CLAUDE_SKILL_DIR}は本文とallowed-toolsのBashルールの両方で置換されます。同じ変数を両方に書くと、ルールが本文の指示するコマンドと一致し、許可の確認なしでスクリプトが走ります。本文で「実行する」と決めたスクリプトは、ここで許可の形にまで落とせます。
なおallowed-toolsの許可は、そのスキルを呼んだターンに限られ、次のメッセージを送ると消えます。組織がallowManagedPermissionRulesOnlyを設定している場合、プロジェクトと個人のスキルのallowed-toolsは無視されます。社内でスキルを配るなら、許可が効かない環境があることを見込んでおきます。
計画を検証してから実行する — 中間ファイルの作り方
複雑でひらめき任せになりやすい作業ほど、ミスが入りやすくなります。有効なのは、plan-validate-executeというパターンです。まず計画を構造化した形式で作らせ、実行の前にスクリプトで検証します。
たとえば、スプレッドシートの内容を基にPDFの50個のフィールドを更新する場面を考えます。検証が無ければ、存在しないフィールドを指したり、値が衝突したり、必須項目を落としたりしかねません。
検証つきの更新フロー
- 1
分析する
元のPDFを調べてフィールドの一覧を出します。
- 2
計画ファイルを作る
変更内容を
changes.jsonのような中間ファイルに書きます。 - 3
計画を検証する
スクリプトで
changes.jsonを検査します。問題があれば、元のPDFには触れずに計画を直します。 - 4
実行して確認する
検証を通った計画だけを適用し、出力を確かめます。
この型が効く理由は4つあります。エラーを早期に拾える、機械的に検証できる、元ファイルに触れずに計画を直せる、エラーが具体的な問題を指す、です。バッチ処理、破壊的な変更、検証ルールが複雑な作業、失敗が許されない作業に向きます。
実装のコツは、検証スクリプトのメッセージを具体的にすることです。例えば「signature_dateが無い。使えるのはcustomer_name、order_total、signature_date_signed」のように、候補まで出します。次に示すのは、この考え方に沿って書いた検証スクリプトの例です。
import json, sys
def validate(plan_path, fields_path):
plan = json.load(open(plan_path))
fields = set(json.load(open(fields_path)))
errors = []
for name in plan:
if name not in fields:
errors.append(
f"Field '{name}' not found. "
f"Available fields: {', '.join(sorted(fields))}"
)
if errors:
print("\n".join(errors))
sys.exit(1)
print("OK")
validate(sys.argv[1], sys.argv[2])Claudeはメッセージに挙がった候補から正しい名前を選び、計画を直して再実行できます。
検証ループをSKILL.mdに書く
スクリプトで検証できる作業では、「実行→エラー→修正→再実行」の検証ループを、手順として本文に書いておきます。手順をチェックリストにし、検証が失敗したらステップ2へ戻ると明示する書き方です。OOXML編集の例がこの型にあたります。
## Document editing process
1. Make your edits to `word/document.xml`
2. **Validate immediately**: `python ooxml/scripts/validate.py unpacked_dir/`
3. If validation fails:
- Review the error message carefully
- Fix the issues in the XML
- Run validation again
4. **Only proceed when validation passes**
5. Rebuild: `python ooxml/scripts/pack.py unpacked_dir/ output.docx`
6. Test the output document「検証が通るまで次へ進まない」と明記している点が肝心です。検証に失敗したときの戻り先が書かれていないと、Claudeは失敗を報告して止まるか、検証を飛ばして先へ進むかの二択になりがちです。
スクリプトが無いSkillでも、同じ形は作れます。検証役をスタイルガイドの文書に置き換え、Claudeが文書を読んで内容を突き合わせる形です。スクリプトにできるなら機械的に、できないなら文書で、という使い分けです。
同梱前に確かめる実行環境の制約
スクリプトを入れる前に、動く環境を決めておく必要があります。パッケージの扱いが環境で違うためです。
| 環境 | パッケージの扱い |
|---|---|
| claude.ai | パッケージの扱いnpmとPyPIからのインストール、GitHubからの取得が可能 |
| Claude API | パッケージの扱いネットワークアクセスなし、実行時のパッケージインストール不可 |
APIで使うSkillが、依存を実行時に入れる前提だと動きません。必要なパッケージはSKILL.mdに書き、コード実行ツールのドキュメントで利用可能かを調べます。「pdfライブラリを使う」のような曖昧な書き方は避け、pip install pypdfと使用コードまで書きます。
APIでSkillを動かす手順はAgent Skills APIクイックスタートに、組織への配布と審査はAgent Skillsの企業導入にあります。
ほかに、スクリプト周りで見落としやすい項目があります。
- パスは
/区切りにする(Windows形式のバックスラッシュは避ける) validate_form.pyのように、ファイル名が内容を表すようにする- 決まった処理は、Claudeに検証コードを書かせず、スクリプトとして置く
- MCPツールを使うなら、
BigQuery:bigquery_schemaのようにサーバー名:ツール名で書く
MCPツールを完全修飾名で書かないと、複数のサーバーがあるときにClaudeがツールを見つけられない場合があります。
同梱スクリプトの設計チェック
ベストプラクティスのチェックリストのうち、コードとスクリプトの項目は次のとおりです。
- スクリプトが問題を解決している(Claudeに丸投げしない)
- エラー処理が明示的で、役に立つ内容になっている
- voodoo constantsがなく、値の根拠がある
- 必要なパッケージが、手順に書かれ、利用可能と確認されている
- スクリプトに明確なドキュメントがある
- パスがすべてスラッシュ区切りになっている
- 重要な操作に検証・確認のステップがある
- 品質が重要な作業にフィードバックループがある
手元のSkillに当てるときの順番
既存のSkillを見直すなら、効果の大きい順に3つ見ます。
- SKILL.mdの各スクリプト言及が、「実行」と「参照」のどちらか読み取れるか
- 例外が出たとき、スクリプトが自分で続行か中断かを決めて出力しているか
- 破壊的な操作の前に、計画を検証する段があるか
1つ目は文面を直すだけで済み、コストが最も低い点検です。3つ目は設計の変更になるため、バッチ処理や書き換えを伴うSkillから着手するのが現実的です。
まとめ
スクリプトを同梱したSkillの出来は、コードの賢さより、失敗したときの振る舞いと文面の曖昧さで決まります。エラーはスクリプトが処理して出力に残し、定数は根拠を書き、SKILL.mdでは実行と参照を動詞で書き分けます。壊れると痛い作業には、計画を検証してから実行する段を挟みます。
Claude Codeで置く場合は、${CLAUDE_SKILL_DIR}を本文とallowed-toolsに同じ形で書いて、実行の意図を許可にまで通します。Skillの全体設計はClaude Code Skills完全ガイド、ツール側の設計はエージェント向けツール設計の原則も合わせて参照してください。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。