AI にターミナルを渡した瞬間、それは「信頼」ではなく「前提条件の整備」の問題に変わる。AI が誤った操作をしないよう願うより、誤った操作ができない構造を先に作るほうがはるかに堅い。この記事では、弊社内製の金融データ基盤で実際に稼働している PreToolUse hook の設計思想と実装パターンを紹介する。
課題:AI 駆動開発における事故の典型
Claude Code のような AI コーディングエージェントに強い権限を与えると、具体的にどんな事故が起きるか。弊社での運用経験と設計検討をもとに整理すると、以下のパターンに収束する。
認証切れ状態での操作実行。 Google Workspace の OAuth トークンには有効期限がある。トークンが切れた状態で gws sheets values update を打つと、認証エラーで処理が中断するだけならまだいいが、ライブラリの実装によっては部分書き込みが起きたまま止まることがある。「途中まで書いた Sheets」は事後で状態を追いにくい。
想定外のパスへの成果物書き込み。 AI はときに /tmp 以下に処理の中間ファイルを置こうとする。一時的な作業領域として便利に見えるが、パイプラインの最終成果物(動画・JSON・スコア)が /tmp に紛れると、再起動・OS クリーンアップで消える。「あのファイルどこに行ったか」は追跡コストが高い。
Destructive な GWS 操作の誤発行。 values clear・files delete・batchUpdate 等は取り返しがつかない。特に AI が「古いデータを消して書き直す」という意図を持って動くとき、スコープを誤ると意図しない範囲を消す。スプレッドシートのバックアップを取り損ねたまま実行されると復元に時間がかかる。
外部公開系 API の誤発行。 YouTube 投稿・Gmail 送信・Slack 通知のような「一度叩いたら戻せない」操作が、ドライランのつもりで書いたコードから意図せず呼ばれることがある。特にプロンプトインジェクションで外部スクレイプ結果に紛れた指示が LLM を介して実行される、というシナリオは「ゼロにする」ことが現実的に難しい。
これらの共通点は、事後で取り返しが付きにくい点だ。事後ロギングで記録を残すことはできても、失われたデータや送信済みのメールは戻らない。
アプローチ:PreToolUse hook で事前ブロック
Claude Code には hook 機構が用意されている。SessionStart・PreToolUse・PostToolUse・SubagentStop といったイベントに任意のシェルスクリプトを紐付け、実行前後に介入できる。
中でも PreToolUse が安全装置として機能する。Bash や Write・Edit ツールが実行される直前に指定のシェルスクリプトが呼ばれ、exit code によって許可と拒否を制御できる。
exit 0 → ツール実行を許可(通常通り進む)
exit 2 → ツール実行をブロック(Claude が別のアプローチを取る)
さらに matcher と if 条件を組み合わせると、全 Bash をブロックするのではなく「gws を含む Bash のみ」「Write ツールで /tmp/ 配下に書くときのみ」といった絞り込みができる。
事後ロギングと事前ブロックは何が違うか。ロギングは「何が起きたかを知る」手段であり、事後に気づく設計だ。PreToolUse は「起きる前に止める」設計で、操作の結果が世界に触れる前に介入できる。AI が誤った操作を生成しても、それがツールとして実行される手前で遮断できる。この差は、外部副作用(Sheets 書き込み・メール送信)が絡む運用において特に大きい。
実例:弊社内製の金融データ基盤での hook 設計
弊社内製の金融データ基盤(株式決算データの収集・構造化・蓄積システム)では、以下の hook を .claude/settings.json で設定している。
| Hook スクリプト | 対象イベント | 守る対象 |
|---|---|---|
pre-gws-auth.sh | PreToolUse(Bash, gws を含む) | GWS 認証切れ状態でのコマンド実行 |
pre-no-tmp.sh | PreToolUse(Write) | /tmp 配下へのパイプライン成果物書き込み |
pre-destructive-gws.sh | PreToolUse(Bash, values clear 等を含む) | Destructive な GWS 操作(delete / clear / batchUpdate) |
pre-business-plan-protect.sh | PreToolUse(Bash) | 事業計画シートへの全 write 系操作 |
pre-publish-gate.sh | PreToolUse(Bash) | YouTube/Gmail/Slack 等の公開・送信 API |
pre-gws-auth.sh の設計
認証チェック hook の構造はシンプルだ。GWS 認証状態を確認するスクリプト(check-gws-auth.sh)を呼び出し、認証が通っていれば exit 0、切れていれば exit 2 でブロックする。
# pre-gws-auth.sh より抜粋
# 認証チェック(0=OK / 1=NG)
if bash "$PROJECT_ROOT/scripts/check-gws-auth.sh" >/dev/null 2>&1; then
exit 0
fi
# 認証切れ → ブロックし、再認証コマンドを stderr に出す
cat >&2 <<'EOF'
GWS authentication is not active or has expired.
Run:
GOOGLE_WORKSPACE_CLI_CONFIG_DIR=... gws auth login
EOF
exit 2
ブロック時に「何をすればいいか」を stderr に出力するのが設計上のポイントだ。ただ「拒否した」だけでは AI も人間オペレーターも次の手が分からない。ブロックメッセージが実行可能な再認証コマンドまで含むことで、止まった理由と回復手順が一セットになる。
pre-no-tmp.sh の設計
こちらは /tmp への書き込みのうち、「パイプライン成果物・中間成果物」に該当するパスパターンのみをブロックする設計だ。
# pre-no-tmp.sh より抜粋
# stdin から書き込み先パスを取得
file_path=$(jq -r '.tool_input.file_path // empty' 2>/dev/null)
# /tmp 配下でなければスルー
case "$file_path" in
/tmp/*) ;;
*) exit 0 ;;
esac
# 禁止パターン判定(パイプライン成果物の典型的なファイル名)
blocked=""
case "$file_path" in
*/input.json|*/analysis.json|*/scripted.json|*/composed.json|*/final.json) blocked="pipeline artifact" ;;
*.mp4) blocked="rendered video" ;;
*-thumb.png) blocked="thumbnail PNG" ;;
*/output/*) blocked="pipeline output dir" ;;
esac
[ -z "$blocked" ] && exit 0
cat >&2 <<EOF
Blocked: writing $blocked to /tmp is not allowed.
Pipeline artifacts must go under output/{topic-id}/ (relative to project root).
EOF
exit 2
すべての /tmp 書き込みをブロックするのではなく、パイプライン成果物に当たるパターンのみを対象にしている点が運用上重要だ。テストフィクスチャや一時バッファは許容し、後から困る種類のファイルだけを止める。「何をブロックするか」の粒度を誤ると、AI が通常業務でも詰まる false positive が増える。
バイパス設計も同様に重要だ。Destructive な GWS 操作や公開系 API については、環境変数(MONTAGE_ALLOW_DESTRUCTIVE_GWS=1 等)を Bash コマンド先頭に明示するとブロックを解除できる。これにより、人間オペレーターが意図して実行する場合はバイパスできるが、AI が自動的に生成するコマンドはブロックされるという非対称な制御が実現できる。
運用してわかった効果と落とし穴
実際に hook を本番環境で動かして数ヶ月が経つ。事故が起きていた可能性があった操作が複数回ブロックされた感覚がある。特に認証トークンの期限切れは気づかずに発生しやすく、pre-gws-auth.sh が働いたことで「認証切れのまま Sheets を更新しようとしていた」ことが可視化されるケースが出てきた。問題が起きる前に止まり、回復可能な状態のまま手を入れられる安心感は、定量では示しにくいが運用者の体感として明確にある。
一方で、hook が増えるにつれて見えてきた課題もある。
false positive の調整コスト。 ブロック条件が広すぎると、本来許可したい操作まで止まる。pre-no-tmp.sh でいえば、「テスト用の一時ファイル」と「パイプライン成果物」の判別をパスパターンだけでやるには限界があり、初期設定の段階でいくつか誤検知があった。条件の調整は「止めたい操作の解析度を上げる」作業で、これに慣れるまで時間がかかる。
hook が増えすぎると AI が詰まる。 hook の数が増えると、AI がどのコマンドを打てるか自分で把握できなくなる。「何かしようとするたびにブロックされる」状態になると、AI はバイパス変数を正しく使えているか判断できず、オペレーターへのエスカレーションが増える。hook は「必要最小限の守りたい対象を明確にして設計する」のが長続きするコツだ。
settings.json の permissions.deny との使い分け。 hook は「コマンドの中身を見て判断する」ための仕組みで、permissions.deny は「パターンマッチで拒否する」より簡易なバリアだ。rm -rf ~/・git push --force・curl ... | sh のような広く危険なパターンは permissions.deny で弾き、より文脈依存の判断が必要なもの(認証状態・ファイルパスのカテゴリ・操作の種類)を hook で扱うのが役割分担として機能する。
まとめ
PreToolUse hook は、AI 駆動開発を本番に乗せるための最終防衛線だ。「AI を信頼する」のではなく、「AI が意図しない操作をしても実害が出ない構造を先に作る」という設計思想が根底にある。hook は事後ロギングと異なり、操作が世界に触れる前に止められる。ブロックメッセージの設計も含めて、「止まった理由と回復手順が一セットになっている」状態にすることが運用上の肝だ。
こうしたガードレールと対になる「ドキュメント自動更新ルール」も別記事で公開している。コード・設定を変えたときに対応ドキュメントを漏らさず更新する仕組みをどう設計しているか紹介している。AI 駆動開発の全体像についてはハブ記事も参照してほしい。