AIに毎日仕事をさせるなら、1回あたりの利用料に上限を付けてください。付けないと、こちらが寝ている間に青天井で使われます。
ただし、この記事で本当に伝えたいのはその先です。
上限は「止める仕組み」であって、「知らせる仕組み」ではありません。
上限に当たってAIが仕事を途中でやめても、多くの場合、誰も気づきません。私は実際に気づけませんでした。上限の決め方と、切れたときに気づく作りを、うちで動いているスクリプトをそのまま出しながら書きます。
なぜ上限がいるのか|1回いくらか、事前には分からない
AIの利用料は「1回いくら」と決まっていません。読んだ資料の量、書いた文章の量、調べ直した回数で変わります。同じ指示を出しても、日によって金額が違います。
人が横で見ているなら、長引いた時点で止められます。問題は自動で動かすときです。うちは記事を書かせる処理を毎朝9時にmacOSのlaunchd(決まった時刻に処理を動かす仕組み)から呼んでいます。誰も見ていません。
指示の書き方をひとつ間違えて、AIが同じ調べ物を延々と繰り返す形になったとします。上限が無ければ、それは止まる理由がありません。気づくのは請求を見たときです。
上限の付け方|1行足すだけ
Claude Codeをスクリプトから呼ぶときは、--max-budget-usd に金額(ドル)を書きます。うちの記事作成スクリプト(~/scripts/media/1本作る.sh)は、この形で呼んでいます。
"$CLAUDE" -p \
--permission-mode acceptEdits \
--allowedTools "Read,Edit,Write,Glob,Grep,WebSearch,WebFetch" \
--settings "$MEDIA_HOME/執筆時の権限.json" \
--max-budget-usd "$BUDGET" \
--no-session-persistence \
"(ここに指示文)"
金額は決め打ちにせず、変数にしてあります。
# 記事1本あたりのAI利用上限(ドル)。環境変数で上書きできる
BUDGET="${MEDIA_BUDGET_USD:-10}"
${MEDIA_BUDGET_USD:-10} は「環境変数が指定されていればそれを使い、無ければ10」という書き方です。普段は10ドル、試すときだけ低くする、という使い分けができます。スクリプト本体を書き換えずに済むので、戻し忘れが起きません。
低すぎる上限は、高すぎる上限より厄介だった
ここからが本題です。上限を低くしておけば安全、ではありませんでした。
2026年8月26日、別に動かしている旅行メディア側の毎日の処理で、上限が10ドルから2ドルに戻ってしまっている状態のまま実行されました。そのときのログの1行目がこれです。
Error: Exceeded USD budget (2)
「2ドルの上限を超えました」。AIは記事を書いている途中で打ち切られました。ここまでは想定どおりです。問題はこの次でした。同じログの続きを、実物のまま貼ります。
毎日運転を開始: 2026-08-26 11:04:53
Error: Exceeded USD budget (2)検品OK
WordPress: output/wordpress/(記事名).html
note: output/note/(記事名).md
管理データ: output/meta/(記事名).json
記事候補の状態: 要確認
英語版検品OK
...
完了: (記事名)
投稿完了: 下書き
毎日運転を完了: 2026-08-26 11:14:29
読んでください。上限に当たったのに、そのあと検品が通り、「完了」と記録され、WordPressに下書きまで投稿されています。
このスクリプトの先頭には、失敗したら止まるための2行がちゃんと書いてあります。
set -eu
set -o pipefail
それでも止まりませんでした。予算切れは、スクリプトから見て「失敗」になっていなかったということです。エラー文は画面に出るのに、終了の合図としては伝わらない。だから後ろの工程が普通に動いてしまう。
上限を付けた安心感が、いちばん危ない。「止まったら気づくはず」は成り立ちません。
止まった位置によっては、書きかけの記事がそのまま公開の列に並びます。金額は守られたのに、成果物は壊れている。青天井より、この静かな失敗のほうが後始末は面倒でした。
上限はいくらにするか|推測で決めない
「相場はいくらですか」と聞かれても、答えられません。使い方で変わるからです。推測で決めずに、自分のところの実費を1回測ってください。
Claude Codeは --output-format json を付けると、結果と一緒に実費を返してきます。その中の total_cost_usd が今回かかった額(ドル)です。ターミナルにそのまま貼れます。
~/.local/bin/claude -p --output-format json "こんにちは、と1行だけ返して" \
| ~/scripts/keiri/.venv/bin/python -c 'import json,sys; print(json.load(sys.stdin)["total_cost_usd"], "ドル")'
これで数字が1つ出ます。あとは本番と同じ仕事を数回やらせて、いちばん高かった回の2〜3倍を上限にするのが私の決め方です。理由は単純で、上限は「普段の費用」ではなく「異常を止める非常ブレーキ」だからです。普段の費用ぴったりに設定すると、少し重い日に毎回引っかかります。
そして実費は毎回記録します。うちの記事作成スクリプトは、AIが終わるたびに実費を読み取って、1行ずつ表に足しています。
COST_USD=$("$MEDIA_PY" -c "
import json,sys
try: print(json.load(open(sys.argv[1],encoding='utf-8')).get('total_cost_usd',0))
except Exception: print(0)
" "$LOG_DIR/生成_$STAMP.json")
COST_YEN=$(hiyou_kiroku "事務自動化ラボ" "記事作成" "$COST_USD")
失敗した回も記録します。途中で打ち切られても、そこまでの分は使っているからです。「失敗したから0円」という数え方をすると、月末に合わなくなります。
正直に書くと、うちはこの記録の仕組みを入れたばかりで、まだ実績が貯まっていません。だから「1本いくらでした」という数字はこの記事に書けません。書けるようになったら別記事にします。
上限は2段構えにする|1回分と、1か月分
--max-budget-usd が守るのは1回分だけです。1回10ドルでも、毎日動けば月に300ドルになります。1回の上限だけでは、月の総額は守れません。
そこでうちは、月の上限を別に持たせています(~/scripts/media/費用ガード.sh)。設定はこの3行だけです。
MEDIA_TSUKI_JOUGEN_YEN="${MEDIA_TSUKI_JOUGEN_YEN:-50000}" # 月の上限(円)
MEDIA_DOLLAR_YEN="${MEDIA_DOLLAR_YEN:-150}" # 円換算レート(概算)
MEDIA_KEIKOKU_WARIAI="${MEDIA_KEIKOKU_WARIAI:-80}" # 何%で警告を出すか
※ 月5万円は2026年8月26日時点のうちの設定値です。円換算は概算レートなので、実際の請求額とは一致しません。あくまで「使いすぎを止めるための目安」として持っています。
やっていることは2つだけです。
- 走る前に、今月いくら使ったか数える。今回の見込み額を足して上限を超えるなら、AIを呼ばずに終わる。
- 走ったあと、実費を1行書き足す。
記録の置き場所は、意図的に事務自動化ラボ側でも旅行メディア側でもない場所(~/.ai-shared/)にしてあります。2つの自動化が同じ財布を見るためです。別々に持つと、片方だけ見て「まだ余裕がある」と誤解します。
判定は、実行前にこう呼ぶだけです。
MIKOMI_YEN=$(awk -v u="$BUDGET" -v r="$MEDIA_DOLLAR_YEN" 'BEGIN{printf "%d", u*r}')
if ! YOSAN_MSG=$(yosan_check "$MIKOMI_YEN"); then
# 上限を超えるので、AIを呼ばずに知らせて終わる
fi
切れたときに気づく作り方|3つ
上限そのものより、こちらのほうが大事です。順番に足せば、今日中に3つとも入ります。
1. 「終わったか」ではなく「増えたか」で判定する
終了コード(処理が終わるときに返す数字。0が成功)を信用しないことにしました。前の章のとおり、予算切れは0で返ってくることがあるからです。代わりに成果物を数えます。
BEFORE=$(find "$MEDIA_KIJI_DIR" -maxdepth 1 -type f -name '*.html' | sort)
# ここでAIに書かせる
AFTER=$(find "$MEDIA_KIJI_DIR" -maxdepth 1 -type f -name '*.html' | sort)
NEW=$(comm -13 <(print -r -- "$BEFORE") <(print -r -- "$AFTER") | head -1)
if [ -z "$NEW" ]; then
echo "記事が作られませんでした。ログ: $LOG"
exit 1
fi
ファイルが1本増えていなければ失敗。これなら、予算切れだろうが指示ミスだろうが、理由を問わず引っかかります。
2. 後始末まで終わったかを確かめる
記事が1本できていても、まだ安心できません。うちの場合、AIには記事のほかに3つの表の更新を頼んでいます。予算が切れると、記事は書けたが表の更新まで届かなかった、という中途半端な状態になります。だから最後に確かめます。
grep -q "^${FILE} " "$MEDIA_LIST" || { echo "★ 記事一覧.tsv に行が足されていません"; NG=1; }
grep -qx "$FILE" "$JUNBAN" || { echo "★ 公開順.txt に行が足されていません"; NG=1; }
ひとつでも欠けていたら、「後始末が終わっていません。上の★を人が直してください」と出して失敗で終わります。途中で力尽きた仕事を、完了として流さないためです。
3. 成功でも失敗でも、必ず1通知らせる
これが決め手でした。何も来ない日があってはいけません。「通知が来ない=うまくいっている」と思い込むと、止まっていることに気づけないからです。
うちは処理の途中でこけても必ず知らせるように、スクリプトの頭でこう仕掛けています。
trap shippai_wo_shiraseru EXIT
trap ... EXIT は「どんな終わり方をしても、最後にこの処理を通す」という指定です。途中で止まっても通知だけは必ず出ます。成功時の通知には、実費と今月の使用状況を必ず入れています。
今回の費用: 約◯◯円
今月のAI利用料: ◯◯円 / 上限 50000円(◯%)
毎日これを見ていれば、月末に驚くことはありません。「使いすぎていないか」を月1回調べる作業そのものが消えます。
上限をゼロにしたときに何が起きるか
ついでに書いておきます。月の上限が0円になった状態で走らせると、AIは呼ばれず、何も作られません。当然です。大事なのは、そのとき黙って終わらないことです。
うちのログには、この1行が残りました。
[2026-08-26 12:03] 予算のため見送り: 今月のAI利用料: 0円 / 上限 0円(0%)
同時に「今月は記事作成を止めました」という通知が出る作りにしてあります。止めたことを知らせない安全装置は、故障と見分けが付きません。止める仕組みを入れるときは、必ず「止めました」と言わせてください。
上限を下げるのは即決、上げるのは相談
最後に運用の決めごとをひとつ。うちではAIへの指示ファイルに、こう書いてあります。
上限を上げるときは人に相談する。下げる・止める方向は相談不要。
AI自身にスクリプトを触らせていると、「予算が足りないので上限を上げておきました」が起こり得ます。お金が増える方向の変更だけは、機械の判断で通さない。逆に下げるのは安全側なので、いつでも勝手にやってよいことにしています。
この非対称が効きます。判断に迷ったとき、機械は必ず安いほうへ倒れます。
まとめ
- AIを自動で動かすなら、1回あたりの上限(
--max-budget-usd)は必ず付ける - 金額は決め打ちにせず変数にする。試すときだけ下げて、戻し忘れを防ぐ
- 上限は「止める仕組み」で「知らせる仕組み」ではない。上限に当たっても、後ろの工程がそのまま動いて「完了」と記録されることが実際にあった
- 上限額は推測で決めない。
--output-format jsonのtotal_cost_usdで実費を測り、いちばん高かった回の2〜3倍を非常ブレーキにする - 1回分の上限だけでは月の総額は守れない。月の上限を別に持ち、走る前に残りを確かめる
- 気づくための3点セット … 成果物が増えたかで判定/後始末まで確認/成功でも失敗でも必ず通知
- 止めたときは必ず「止めた」と知らせる。黙って止まる安全装置は故障と区別が付かない
- 上限を上げるのは人の決裁、下げるのは即決。この非対称をルールにしておく
まずは --max-budget-usd の1行を足してください。そのうえで、今日の自動化から「今日は何もしませんでした」という通知が来るかどうかを確かめてみてください。来ないなら、止まっても気づけない状態です。
なお「上限に当たったのに完了と記録された」件は、終了コードの扱いという、もっと根の深い問題とつながっています。続きは 自動化が「成功」と嘘をつくとき|終了コードが当てにならない話 をどうぞ。


コメント