API課金のスクリプトを、確認なしで走らせてクレジットを枯渇させたことが2回ある。それ以来、CLAUDE.mdには「課金するスクリプトは、コストを示して許可を取ってから実行」と書いてある。
書いてあるだけだ。守るかどうかはClaude次第になる。
2026年10月、Claude CodeにModsが入った。Claude Codeの中で動く関数で、ツールの呼び出しを止められる。だったらルールを文章ではなく仕組みにできる。そう思って2つ作った。
- Claude Code 2.1.288のModsで、API課金スクリプトを人が許可するまで止めるガードと、コンテキスト使用率の表示・50%警告の2つを作った。
- ガードは実セッションでスクリプト名を含むコマンドを止めた。使用率の表示は動いたが、表示だけなら既存のステータスラインでできる。
- テストで3回つまずき、公式ドキュメントを後から読んで、ガードにはもっと良い作り方があると知った。
Modsは「Claude Codeの中で動く関数」
hooksとの違いは、動く場所
Claude Codeには前からhooksがある。設定ファイルに書いたシェルコマンドやHTTPリクエストを、ツール実行の前後などで呼ぶ仕組みだ。
Modsの公式ドキュメントは、両者の違いを「どこで動くか」で説明している。hooksは外からスクリプトを呼ぶ。ModsはJavaScriptかTypeScriptの関数で、Claude Codeのプロセスの中で動く。だから画面にパネルを出したり、Claudeのターンを挟まずに動く独自コマンドを足したりできる。
| hooks | Mods | |
|---|---|---|
| 中身 | シェルコマンド・HTTPリクエスト | JavaScript / TypeScript の関数 |
| 動く場所 | Claude Codeの外から呼ばれる | Claude Codeのプロセスの中 |
| ツール呼び出しを止める | できる | できる |
| 画面に表示する・人に聞く | できない | できる |
※ 公式ドキュメントの比較表(2026年10月時点)から、この記事に関係する行だけを抜いた。
使えるのはClaude Code v2.1.287以降で、既定で有効になっている。筆者の環境は2.1.288だった。
ツール呼び出しを止めるだけなら、hooksでもできる。/goalの記事で書いたとおり、/goalの実体もStop hookで、止める・通すの判定はhooksの得意分野だ。Modsでなければできないのは、人に何かを見せる・聞く部分だ。これは後で効いてくる。
書いたのはClaude、頼んだのは筆者
公式のCreate a modには「Claude Codeのセッションで欲しいものを説明すれば、Claudeが書く」とある。実際そうした。
Claude Codeには、Modsを書くためのplugin-authoringというスキルが最初から入っている。これが読み込まれると、作業用フォルダが監視される。最初のファイルが書かれた瞬間に「このセッションでホットリロードを有効にするか」と1回だけ聞かれ、有効にすると、以降はファイルを直すたびにターンの終わりで読み込み直される。
1つのModは3ファイルで済む。
.claude-plugin/plugin.json: 名前と説明hooks/hooks.json: コード本体のファイル名hooks/register.ts: 本体。on(イベント名, 関数)でフックを登録する
1つ目:API課金スクリプトを止める api-cost-guard
止めたいのは3本だけ
筆者のブログ自動化パイプラインには、Anthropic APIを直接呼ぶスクリプトが3本ある。ネタ収集・構成案・本文生成で、本文生成は特に高い。
対話で使っている分はサブスクの枠で済む。従量課金が乗るのは、こうしてAPIを直接呼ぶ処理だけだ。経緯は無人処理でだけAPI課金が出る話に書いた。
なので、止める対象はこの3本に絞った。それ以外のコマンドには一切触らない。
仕組み:人が /api-ok を打つまで通さない
const COSTLY = /\b(01b_fetch_topics_multi|02b_generate_structure|04a_generate_article)\.py\b/
const WINDOW_MS = 10 * 60 * 1000
let armedUntil: number | null = null
export const register: Register = on => {
on('session.start', async ($, e, next) => {
await $.command.register({ name: 'api-ok', description: 'Claude API課金スクリプトの実行を1回・10分以内に限り許可する' })
return next(e)
})
on('command.run', { command: 'api-ok' }, async $ => {
armedUntil = (await $.clock.now()) + WINDOW_MS
return { text: 'Claude API課金スクリプトを1回だけ許可した(10分以内)。' }
})
on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
const hit = e.command.match(COSTLY)
if (!hit) return next(e)
if (armedUntil === null || (await $.clock.now()) > armedUntil) {
armedUntil = null
return { deny: `${$.plugin.name}: ${hit[1]}.py は Claude API を課金する。想定トークン数とコストをユーザーに示し、ユーザーが /api-ok を打ってから再実行すること。` }
}
armedUntil = null
return next(e)
})
}
Claudeが課金スクリプトをBashで動かそうとすると、tool.call のフックがそれを横取りする。許可されていなければ deny を返して止める。止めた理由の文章は、そのままClaudeに返る。
許可は /api-ok という独自コマンドで出す。打つと10分間、1回だけ通る。通ったら許可は消える。
graph TD
A["ClaudeがBashを実行"] --> B{"課金スクリプト名を含む"}
B -- "いいえ" --> C["そのまま実行"]
B -- "はい" --> D{"許可が残っている"}
D -- "はい" --> E["実行して許可を消す"]
D -- "いいえ" --> F["止めて理由を返す"]
G["人が /api-ok を打つ"] --> H["10分間・1回だけ許可"]
H --> D
実セッションで止まった
読み込まれたあと、スクリプトを実行しない無害なコマンドで試した。スクリプト名を echo するだけだ。
echo "dry: scripts/04a_generate_article.py"
結果はこうだった。
api-cost-guard: 04a_generate_article.py は Claude API を課金する。想定トークン数とコストをユーザーに示し、ユーザーが /api-ok を打ってから再実行すること。
echoは何も実行しないのに止まった。ファイル名が含まれていれば止める作りだからだ。
ここは直さなかった。止めすぎて困るのは、確認が1回増えることだけだ。逆に、すり抜けたら課金が発生する。どちらに倒すかを選ぶなら、止めすぎる側に倒す。
2つ目:コンテキスト50%を知らせる ctx-watch
50%ルールを、目で見えるようにしたかった
筆者はCLAUDE.mdに「コンテキスト使用率が50%を超える前に /compact」と書いている。理由はコスト削減の記事で書いた50%ルールのとおりで、50%を超えると指示の取りこぼしが増える。
困るのは、今何%なのかを自分が見ていないことだ。そこで、Claudeの1ターンが終わるたびに使用率を取り、画面下のステータス行に出すことにした。50%を超えた最初の1回だけ、トーストで知らせる。使用率は $.session.usage() で取れる。

先頭の警告マークと「ctx-watch:」はClaude Code側が付けた。どのModの表示かが分かるようになっている。
正直に言うと、表示だけなら既存機能で足りた
作り終わってから公式ドキュメントを読んで知った。使用率の常時表示は、Modsを使わなくてもステータスラインのカスタマイズでできる。公式のサンプルModにも、コンテキストの減り方を予報する「token-weather」がある。
ctx-watchで新しいのは、50%を越えた瞬間のトーストだけだ。これも、ステータスラインの数字を見る習慣があれば要らない。2つ作って、手応えがはっきり違った。
テストで3回つまずいた
Modsには claude plugin test という自動テストの仕組みがあり、セッションを開かずに動かせる。テストの公式ページどおり、Claude Code側の応答は「スタブ」で差し替える。ここで3回つまずいた。
| 症状 | 原因 | 直し方 | |
|---|---|---|---|
| 1回目 | 許可なしでも課金スクリプトが通った | 「許可なし」を armedUntil = 0 で表していた。テストの時計は0から始まり、「今(0)が期限(0)を過ぎたか」が偽になった | 「許可なし」を null で表す |
| 2回目 | 止めたのに「止まっていない」と判定された | 止めた結果はエラー扱いで返ると思い込んでいた。テストから呼ぶと { deny: 理由 } の形で返る | 実際の値を出力させて、判定を deny に合わせる |
| 3回目 | スタブが「{ value } も { deny } も返していない」と弾かれた | 使用率を返すスタブで、値を包まずに返していた | { value: ... } で包む |
1回目は、実際の時刻ならまず起きない。それでも、時刻に頼った書き方だったのは事実で、テストがなければ気づかなかった。3回目のメッセージと、ステータス表示のスタブが無いときの「実装がない」というメッセージは、公式ページにそのまま載っている。
最終的に、課金ガードが3件、ctx-watchが2件のテストを通った。
後から公式を読んで分かったこと
ガードは「止める」より「聞く」が正解だった
公式のイベントのページに、「ユーザーが決めるまでツール呼び出しを保留する」書き方が載っていた。フックの中で $.ui.ask を呼ぶと、画面に質問を出し、答えが来るまで実行を待たせられる。公式の例は、質問が閉じられたら拒否に倒す書き方になっている。
| 筆者の作り(/api-ok) | 公式の例($.ui.ask) | |
|---|---|---|
| 止めたあと | Claudeに理由を返す | その場で人に質問を出す |
| 人の操作 | /api-ok を打ち、Claudeに再実行を頼む | 「実行する/しない」を答える |
| Claudeの実行 | 2回(止められる・通る) | 1回 |
筆者の作りは往復が多い。$.ui.ask なら、その場で聞いて終わる。人に聞く部分はhooksではできないので、ここがModsを使う一番の理由になる。
先に公式を読んでいれば、最初からこちらで作っていた。次に直すのはここだ。
Modsはサンドボックスの外にある
公式ドキュメントは、Modsが自分の権限で動くことをはっきり書いている。ファイルの読み書き、プログラムの起動、ネットワーク通信ができ、環境変数やAPIキーも読める。サンドボックスを有効にしても、Modが起動したプロセスはその外で動く。
筆者は以前、プラグイン経由で仕込まれたhookにツールの出力を書き換えられたことがある。経緯はhookにAIの調査を汚染された件に書いた。Modsはそれより内側で動くので、他人のModを入れるときの注意はさらに重くなる。
入れる前に中身を確かめる方法はある。claude plugin validate に掛けると、実行せずに「どのイベントを拾い、何を呼ぶか」が一覧で出る。課金ガードならこうなった。
./register.ts hooks: session.start, command.run{command=api-ok}, tool.call{tool=Bash}
./register.ts calls: $.clock.now, $.command.register, $.ui.status
Bashの呼び出しを横取りしていて、使うのは時計とコマンド登録とステータス表示だけ。ファイルもネットワークも触らない。他人のModなら、ここに $.fs や $.process が出ていないかを先に見る。
よくある質問(FAQ)
- QModsとhooksは、どう使い分けますか?
- A
止める・通すを判定するだけならhooksで足ります。筆者の課金ガードも、判定の部分はhooksで作れました。Modsが要るのは、画面に表示する、人にその場で聞く、Claudeのターンを使わない独自コマンドを足す、といった人に向けた部分です。課金ガードを作り直すなら、$.ui.askで実行前に聞く形にします。
- QプログラミングができなくてもModsは作れますか?
- A
作れます。筆者の2つのModも、Claude Codeのセッションで欲しい動きを説明し、Claudeに書かせました。ただし、何を止めて何を通すかは自分で決める必要があります。課金ガードでは、止める対象をAPIを直接呼ぶスクリプト3本に絞り、スクリプト名を含むコマンドは実行しないechoでも止める、という判断を人がしています。
- Q他人が作ったModを入れても大丈夫ですか?
- A
中身を見てからにしてください。Modsはサンドボックスの外で、自分の権限で動き、APIキーも読めます。入れる前に claude plugin validate に掛けると、実行せずに拾うイベントと呼ぶ機能が一覧で出ます。筆者の課金ガードは時計・コマンド登録・ステータス表示しか呼んでいません。$.fsや$.processが出ていたら、何に使うのかを確かめます。
- QWindowsでもModsは動きますか?
- A
筆者はWindows 11 Home・Claude Code 2.1.288のターミナルで、2つとも動かしました。公式ドキュメントによると、デスクトップアプリのCodeタブでも動きますが、WSLのセッションでは動きません。VS Code拡張のチャット欄やclaude -pでは、処理は動くものの画面への表示は出ません。課金ガードのような止めるだけのModなら、表示が出ない場所でも効きます。
課金を止める仕組みはModsで作る価値がある。表示だけなら要らない
2つ作って、片方は実際に効き、片方は既存機能の焼き直しだった。
課金ガードは、CLAUDE.mdに書いた一文を、破れない仕組みに変えた。実セッションで実際に止まった。表示のほうは、ステータスラインで同じことができる。
次は、課金ガードを $.ui.ask で実行前に聞く形に作り直す。Modsで何を作るか迷ったら、文章で書いたのに守られなかったルールから選ぶといい。


