Claude Code Mods|課金ガードを自作した実測

週末起業ラボのアイキャッチ画像:Claude CodeのModsで課金ガードとコンテキスト警告を自作した記録を解説するブログ記事 AI

API課金のスクリプトを、確認なしで走らせてクレジットを枯渇させたことが2回ある。それ以来、CLAUDE.mdには「課金するスクリプトは、コストを示して許可を取ってから実行」と書いてある。

書いてあるだけだ。守るかどうかはClaude次第になる。

2026年10月、Claude CodeにModsが入った。Claude Codeの中で動く関数で、ツールの呼び出しを止められる。だったらルールを文章ではなく仕組みにできる。そう思って2つ作った。

TL;DR / 三行要約 7分 MIN READ · UPDATED 2026-10-04
  1. Claude Code 2.1.288のModsで、API課金スクリプトを人が許可するまで止めるガードと、コンテキスト使用率の表示・50%警告の2つを作った。
  2. ガードは実セッションでスクリプト名を含むコマンドを止めた。使用率の表示は動いたが、表示だけなら既存のステータスラインでできる。
  3. テストで3回つまずき、公式ドキュメントを後から読んで、ガードにはもっと良い作り方があると知った。
RESULT — 2つ作成・実機で動作 TOOL — Claude Code COST — 0円

Modsは「Claude Codeの中で動く関数」

hooksとの違いは、動く場所

Claude Codeには前からhooksがある。設定ファイルに書いたシェルコマンドやHTTPリクエストを、ツール実行の前後などで呼ぶ仕組みだ。

Modsの公式ドキュメントは、両者の違いを「どこで動くか」で説明している。hooksは外からスクリプトを呼ぶ。ModsはJavaScriptかTypeScriptの関数で、Claude Codeのプロセスの中で動く。だから画面にパネルを出したり、Claudeのターンを挟まずに動く独自コマンドを足したりできる。

hooksMods
中身シェルコマンド・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 を打つまで通さない

※ このコードは筆者が動作確認済み(環境: Windows 11 Home / Claude Code 2.1.288)。掲載は抜粋で、import文とステータス表示の行を省いています。実際の挙動は実行環境によって異なる場合があります。

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() で取れる。

Claude Code画面下のステータス行に「ctx-watch: ctx 34%」と表示されている

先頭の警告マークと「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)

Q
Modsと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が出ていたら、何に使うのかを確かめます。

Q
Windowsでも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で何を作るか迷ったら、文章で書いたのに守られなかったルールから選ぶといい。

ムラサキ
ムラサキ
SE歴20年 · 金融系システム開発課長 · 週末起業1年目
システムエンジニア歴20年。金融系システム開発の課長として勤務する傍ら、AIと副業の最前線を実践レポート。Claude Code Max 5xプランを日常業務・副業開発に活用中。一次情報と実測値にこだわった記事を発信。
20 YRS SE
8 MOS 副業
55 ARTICLES
タイトルとURLをコピーしました