はじめに
この記事は、Claude Code を「攻略する」話ではありません。
すでに業務に入れていて、複数のリポジトリ・複数の文脈を並行で回し始めた人が、事故らないために何を仕込むかという運用の話です。
きっかけは、自分が複数のサービスを並行で開発・保守するようになったことでした。
本業のリポジトリ、副業のリポジトリ、個人開発のリポジトリ。
それぞれに別のブランチ運用、別のデプロイ先、別の秘匿情報があります。
Claude Code はこの「並行作業」と相性が良い一方で、文脈が混ざった瞬間にやらかす範囲も広がります。
導入直後は「とにかく速い」ことに目が向きがちです。
ところが運用が一巡すると、関心は静かに移ります。
「どうやって速く回すか」から「どうやって事故らせずに回すか」へ、です。
この記事は後者に振り切っています。
この記事で扱うこと・得られること
- ノーガードで Claude Code を回したときに起きやすい事故の類型
- 事故を機械的・運用的に防ぐガードレールを7項目(各項目に「良くない例 → 対処 → なぜ良くなるか」を付けます)
- 各ガードレールが効く理由を、攻撃面・失敗の構造・人間のレビューとの対比から一段深く掘り下げます
- 複数リポジトリならではの落とし穴(文脈取り違え・誤コミット先)
- ガードレールを入れてから運用がどう変わったか、残った課題
逆に、Claude Code のインストール方法や基本操作は扱いません。
そこは公式ドキュメントが整っているので、そちらに譲ります。
対象読者・前提
- Claude Code を業務で使い始め、複数リポジトリを横断している方
- AI に任せる範囲を広げたいが、不可逆操作(削除・公開・送信・課金)が怖い方
- チームに導入するにあたり、安全運用のたたき台がほしい方
前提として、いくつかおことわりを先に書きます。
- Claude Code は更新が速いツールです。hook や permission mode の仕様は変わりうるため、設定例は「考え方の最小サンプル」として読んでください。実際に入れるときは公式ドキュメントで最新仕様を確認していただければと思います。
- ここで紹介するのは「自分の環境ではこう運用して落ち着いた」という一例です。ベストプラクティスは状況依存なので、断定はしません。合わない部分は読み替えてください。
- コード・設定・ディレクトリ名はすべて架空のものに置き換えています。実在の環境を再現したものではありません。
- 設定例の一部は生成 AI の助けを借りて整理しましたが、仕様は公式ドキュメントで裏取りし、自分の環境で意図どおり動くことを確認しています。
動作確認の前提は、Claude Code の2026年6月時点の公式ドキュメント(Hooks / Permissions / Subagents)です。hook スクリプトの例は bash と jq を前提にしています。挙動はバージョンで変わりうるので、最新仕様の確認をおすすめします。
それでは、まず「ガードレールがないと何が起きるか」から見ていきます。
全体像
先に、これから作る安全装置の全体像を1枚で示します。
要点は「読み取りや小さな編集は速いまま通し、不可逆・高リスクな操作だけを段階的に止める」という多層構造です。
図のとおり、確認の強さを決める permission mode と、機械的に止める hook を重ねます。
片方が緩んでも、もう片方が残るのが狙いです。
ノーガードで起きる事故の類型
ガードレールの話に入る前に、防ぎたい相手を具体化します。
抽象的に「危険です」と言っても腹落ちしないので、自分や周囲で実際に見聞きした「ヒヤリ」を型にして並べます。
個別の固有名詞は伏せ、再現条件だけ残します。
類型1: 誤削除・誤上書き
一番こわいのは、消えたら戻らない操作です。
「不要なログを掃除して」とだけ頼んだら、想定より広い範囲をまとめて消そうとした、という場面があります。
Claude Code 自体は確認を挟む設計ですが、bypassPermissions や広い allow ルールで権限を緩めた状態で回していると、その確認が省かれます。
git 管理下のファイルなら復元できることも多いです。
ところが .env や生成物、git 管理外のローカルデータは戻りません。
「戻せると思っていたものが、実は戻せなかった」が事故の本体です。
ここには、AI 特有の失敗構造が一段あります。
人間なら rm -rf logs/ を打つ前に、logs/ の中身を一瞬イメージします。
AI も文脈次第で確認しますが、指示が曖昧だと「掃除して」を最も素直に満たすコマンドへ寄ることがあります。
意図の最小充足が、最大の破壊につながりうる。ここが落とし穴です。
類型2: 機密の混入
複数リポジトリを横断していると、ある文脈で見た秘匿値が別の文脈の出力に紛れ込むリスクが出ます。
たとえば、API キーやクライアント名が、調査メモやコミットメッセージ、記事の下書きに残ってしまうケースです。
これは「AI が悪意で漏らす」話ではありません。
人間が貼ったものを、AI が素直に引き継いでしまう話です。
入力に秘匿情報が混ざっていれば、出力にも混ざりうる。ここを構造で止める必要があります。
攻撃面の観点でも、この型は見逃せません。
AI に外部の文書やコードを読ませると、その中に「秘匿値を出力せよ」という趣旨の文が紛れていることがあります。
いわゆるプロンプトインジェクションです。
人間の注意だけでは、こうした「読んだ内容に操作される」経路は塞ぎきれません。
類型3: 未読のまま編集する
「このファイルのバグを直して」と頼んだとき、ファイル全体を読まずに一部だけ書き換えると、別の前提を壊すことがあります。
たとえば、ある関数の戻り値の形を変えたら、同じファイルの別の呼び出し元が静かに壊れる、というパターンです。
これは AI 特有の問題ではなく、人間のレビューでも起きます。
ただ、AI は「速く書けてしまう」ぶん、読まずに書く誘惑が強い。
速度が出る道具ほど、読む工程を飛ばしたときの被害が早く広がります。
類型4: 検証スキップ
実装が終わった直後、テストもビルドも型チェックも通さずに「完了しました」と報告される。
そのまま信じてコミットすると、後で CI が落ちます。
コンテキストが長くなって残り容量が苦しくなると、この「検証飛ばし」が起きやすくなります。
「動くはず」で止めるのが一番危険です。
動く根拠(テスト結果・ビルドログ)が出るまでは、完了ではありません。
類型5: 文脈・コミット先の取り違え(複数リポジトリ特有)
これは後半で詳しく扱いますが、複数リポジトリを開いていると、
「A リポジトリの話をしていたつもりが、B リポジトリのファイルを触っていた」が起きます。
コミット先を間違えれば、別プロジェクトの履歴に無関係な変更が混ざります。
以上5類型が、これから紹介するガードレールが防ぎたい相手です。
共通点は「速いがゆえに、確認・検証・文脈確認という遅い工程が飛びやすい」こと。
だからガードレールは、その遅い工程を機械と運用で強制的に呼び戻す仕組みになります。
ガードレール1: 不可逆操作は人間に確認させる
なぜ必要か
不可逆操作とは、やったら戻せない(あるいは戻すコストが極端に高い)操作です。
具体的には、削除・上書き・送信・公開・コミット/プッシュ・課金。
ここを「AI の判断だけで進ませない」のが、ガードレールの一丁目一番地です。
理由はシンプルで、間違いの期待値が非対称だからです。
読み取りの間違いはやり直せます。
削除や公開の間違いは、やり直せないか、信用や金銭を失います。
期待値が非対称なら、対策の重みも非対称にするのが筋です。
良くない例
権限を全面的に緩めて、確認なしで何でも実行できる状態にしてしまう運用です。
速度は出ます。
ところが、消す・出す・送る・課金する、までノーチェックで通ると、1回の取り違えで取り返しがつかなくなります。
# 良くない運用イメージ(概念。実コマンドではありません)
すべての操作を確認なしで自動承認する
→ 速いが、削除・公開・課金まで素通りしてしまう
Claude Code には bypassPermissions という、確認をほぼ飛ばすモードもあります。
公式ドキュメントでも、コンテナや VM など「壊れても影響が閉じる環境」での利用に限るよう注意が書かれています。
日常のリポジトリ作業で常用するモードではない、と理解しています。
対処
不可逆操作だけは、必ず人間の確認を挟む運用にします。
権限モードを「編集は自動で受け入れるが、危険な操作は確認する」あたりに置くのが現実的な落とし所です。
Claude Code には複数の permission mode があり、確認の強さを段階的に選べます。
2026年6月時点での代表的なモードは次のとおりです(モード構成は更新されうるので、最新版を公式ドキュメントで確認してください。ここに挙げたのは代表例で、すべてではありません)。
| モード | ざっくりの挙動 | 向いている場面 |
|---|---|---|
default |
ツールの初回使用ごとに確認する | 慎重に進めたいとき |
acceptEdits |
ファイル編集と一部の安全な操作を自動承認 | 編集を速く回したいとき |
plan |
読み取りと読み取り専用コマンドだけ。ソースは編集しない | まず読んで計画したいとき |
bypassPermissions |
確認をほぼ飛ばす(一部は安全弁あり) | 隔離環境に限定 |
このうち plan モードはとくに有用です。
実際に触る前に「何をするつもりか」を提示させ、編集系ツールを使わせない動きになります。
不可逆操作の前に一拍置く、という本ガードレールの思想と素直にかみ合います。
permission mode は「全体の確認の強さ」を決める粗いダイヤルです。
一方で、特定の操作だけをピンポイントで止めたいなら、設定ファイルの権限ルールが使えます。
ルールは deny(拒否)→ ask(確認)→ allow(許可)の順に評価され、最初に一致したものが効きます。
たとえば、編集や Bash は許しつつ、強制 push だけ確認させる、といった指定です。
{
"permissions": {
"allow": [
"Bash(git commit *)"
],
"ask": [
"Bash(git push *--force*)"
]
}
}
加えて、運用ルールとして CLAUDE.md などのプロジェクト指示ファイルに、次のような一文を置いておきます。
## 確認が必要な操作
以下は実行前に必ず人間に確認する。AI 単独で進めない。
- 削除・上書き(git 管理外を含む)
- 外部送信・公開
- コミット / プッシュ
- 課金が発生する操作
CLAUDE.md の指示は「AI が何をしようとするか」を形づくるものです。
公式ドキュメントにあるとおり、権限の境界そのものは権限ルールや hook が決めます。
自然言語の注意書きだけに頼らず、機械的な仕組みと併用してください。
なぜ良くなるか
これで「速くてよい操作」と「止まるべき操作」が分離されます。
読み取りや小さな編集は速いまま進み、不可逆操作だけ人間のレビューが入る。
速度と安全のトレードオフを、操作の種類ごとに切り分けられるのが効きます。
ひとつ補足します。
このルールは「AI を信用していないから」ではありません。
人間同士でも、本番削除やリリースには確認を挟むのが普通です。
それと同じ作法を、AI を相棒にしたときにも持ち込むだけです。
ガードレール2: 危険な Bash は hook で機械的に遮断する
なぜ必要か
ガードレール1は「運用ルール」と「権限ルール」でした。
ただ、運用ルールは破られます。
人間も AI も、急いでいるときほどルールを飛ばします。
だから、本当に危険なものはコードで物理的に止めるのが確実です。
ここで使えるのが Claude Code の hook です。
hook は、ツール実行の前後など決まったタイミングで自前のスクリプトを差し込める仕組みです。
公式ドキュメントには PreToolUse や PostToolUse をはじめ、多くの hook イベントが列挙されています。
とくに PreToolUse hook は、ツールが実行される直前に割り込んで、実行を拒否できます。
権限ルールとの違いも押さえておくとよいです。
権限ルールは「コマンド文字列のパターン」で許可・確認・拒否を決めます。
hook は任意のスクリプトなので、パターンでは表しにくい条件(複数要素の組み合わせや外部状態)でも判定できます。
公式ドキュメントも、引数で絞るような権限ルールは壊れやすいと注意し、確実な遮断には hook を勧めています。
良くない例
rm -rf のような破壊的コマンドを、ルール(自然言語の注意書き)だけで止めようとする運用です。
注意書きは効くこともありますが、保証はありません。
コンテキストが長くなると、冒頭の注意書きの効きが弱まることもあります。
# 良くない運用イメージ
CLAUDE.md に「rm -rf は使わないこと」と書くだけ
→ 守られる確率は上がるが、機械的な保証はない
対処
PreToolUse hook で、危険コマンドを検出したら実行を拒否します。
公式ドキュメント(2026年6月時点)によれば、PreToolUse hook には2通りの止め方があります。
ひとつは終了コード 2 で返す方法、もうひとつは終了コード 0 で permissionDecision が "deny" の JSON を返す方法です。
まず settings.json 側の設定です。
matcher でツール名(ここでは Bash)を指定し、フックスクリプトを紐付けます。
ここではすべての Bash 呼び出しをスクリプトに通し、危険パターンの判定はスクリプト側でまとめて行います。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-bash.sh"
}
]
}
]
}
}
個々のハンドラに if(権限ルール構文)を付けると、対象をさらに絞れます。
ただし if で絞ると、その条件に一致しない種類のコマンドではハンドラ自体が起動しません。
1つのスクリプトで複数のパターン(rm -rf と force push など)をまとめて検査したい場合は、if を付けないでください。
if を使うなら、スクリプトの検査対象とコマンド種類を一致させます。
次に、フックスクリプトの最小例です。
標準入力に渡る JSON からコマンド文字列を取り出し、危険パターンに当たったら終了コード 2 で拒否します。
終了コード 2 のときは、標準エラー出力のテキストが「拒否理由」として AI に返ります。
#!/usr/bin/env bash
# guard-bash.sh
# 危険な Bash を PreToolUse で遮断する最小サンプル(架空・最小化)
set -euo pipefail
# stdin の JSON から実行されるコマンド文字列を取り出す
command_text="$(jq -r '.tool_input.command // empty')"
# 遮断したい危険パターン(必要に応じて足す)
dangerous_patterns=(
'rm[[:space:]]+-rf'
'git[[:space:]]+push[[:space:]]+.*--force'
':\(\)\{.*\};:' # フォークボム的なパターン
)
for pattern in "${dangerous_patterns[@]}"; do
if printf '%s' "$command_text" | grep -Eq "$pattern"; then
# stderr に理由を出すと、AI 側に「なぜ止まったか」が伝わる
echo "Blocked by guard-bash.sh: matched /$pattern/" >&2
exit 2 # 終了コード 2 で実行を拒否
fi
done
exit 0 # 該当なし。通常の権限フローに任せる
JSON で返す書き方も覚えておくと、拒否理由を構造化して渡せます。
公式ドキュメントの例にならい、hookSpecificOutput の下に permissionDecision を置きます。
この形では終了コードは 0 のまま、JSON で判断を伝えます。
#!/usr/bin/env bash
# guard-bash-json.sh — JSON で deny を返す版(架空・最小化)
set -euo pipefail
command_text="$(jq -r '.tool_input.command // empty')"
if printf '%s' "$command_text" | grep -Eq 'rm[[:space:]]+-rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "rm -rf is blocked by guard hook"
}
}'
exit 0
fi
exit 0 # 該当なし
このスクリプトを実行可能にして配置すれば、rm -rf を含む Bash 呼び出しは実行前に弾かれます。
実行イメージは次のとおりです。
# AI が rm -rf を含むコマンドを出そうとすると…
Blocked by guard-bash.sh: matched /rm[[:space:]]+-rf/
→ コマンドは実行されず、AI には拒否理由が返る
なお matcher はツール名の完全一致のほか、Edit|Write のようなパイプ区切りや正規表現も使えます。
正規表現は mcp__memory__.* のように MCP ツール群をまとめて対象にもできます。
hook はシェルの構造まで完全には解釈しません。
たとえばコマンドを変数経由や別名で組み立てられると、単純な文字列パターンはすり抜けます。
hook を「すべてを止める壁」と過信せず、後述する権限ルールや sandbox と重ねるのが安全です。
なぜ良くなるか
最大の利点は、自然言語の注意書きより強制力が高い点です。
注意書きは AI が文脈を見落とせばすり抜けますが、hook はツール呼び出しの直前に機械的な検査を挟めます。
hook は実行経路に挟まるので、文脈に左右されにくくなります(ただし後述のとおり、文字列検査そのものは万能ではありません)。
公式ドキュメントでも、終了コード 2 の hook は許可ルールより優先して止まる、と説明されています。
もうひとつ。hook は「最後の砦」として置くのがコツだと考えています。
日常的な確認はガードレール1(運用+permission mode+権限ルール)で受け、
それでも来てしまった本当にまずいものだけを hook で落とす。
二段構えにすると、片方が緩んでももう片方が残ります。
ひとつだけ注意です。
hook を増やしすぎると、正当な操作まで止まって作業が滞ります。
止める対象は「不可逆かつ高リスク」に絞るのが、長く使ううえで大事だと感じています。
ガードレール3: コードを読まずに書かせない(Research-first)
なぜ必要か
類型3で触れた「未読のまま編集する」事故を、入口で止めるガードレールです。
原則はシンプルで、変更する前に対象ファイルを読む。
読んでいないファイルは編集しない。
人間のレビューでも「差分だけ見て周辺を見ない」と事故ります。
AI は速く書けるぶん、この油断が出やすい。
だから「読んでから書く」を明示的なルールにします。
失敗の構造を一段掘ると、原因は「局所最適」です。
頼まれた1行を満たすだけなら、周辺を読まなくても変更は書けます。
ところがコードは、戻り値の形や引数の数といった「契約」で他とつながっています。
契約を読まずに局所だけ直すと、契約の向こう側が静かに壊れる。これが未読編集の本質です。
良くない例
ファイル全体の前提を確認せず、指示された箇所だけ書き換えるパターンです。
コードレビューでよく出る「直したつもりが壊す」変更を、最小例で示します。
// 変更前: 呼び出し元は { total } を受け取る前提
function summarize(items) {
const total = items.reduce((acc, x) => acc + x.price, 0);
return { total };
}
// 別の場所(未読だった)でこう使われている
const { total } = summarize(cart);
renderTotal(total);
ここで「合計だけでなく件数も返して」と頼まれ、戻り値の形だけ変えたとします。
// 良くない変更: 戻り値の形を変えたが、呼び出し元を確認していない
function summarize(items) {
const total = items.reduce((acc, x) => acc + x.price, 0);
// 配列で返すように変えてしまった
return [total, items.length];
}
呼び出し元は { total } を分割代入で受け取る前提でした。
この変更で total は undefined になり、表示が壊れます。
呼び出し元(未読のファイル)を読んでいれば防げた事故です。
対処
「変更前に対象ファイルと、その呼び出し元を読む」をルール化します。
プロジェクト指示ファイルに、次のような原則を書いておきます。
## 実装の原則(Research-first)
- 変更する前に、対象ファイルを必ず読む。
- 読んでいないファイルは編集しない。
- 戻り値・引数・公開インターフェースを変えるときは、参照箇所を先に洗う。
- 推測で書かない。確認を最優先する。
そのうえで、戻り値の形を変えるなら、影響範囲を確認してから既存の前提を保ちます。
// 良い変更: 既存の { total } を壊さず、件数を足す形にする
function summarize(items) {
const total = items.reduce((acc, x) => acc + x.price, 0);
return { total, count: items.length };
}
// 呼び出し元は従来どおり { total } を受け取れる
const { total, count } = summarize(cart);
なぜ良くなるか
「読む」工程を挟むと、変更の影響範囲が見えます。
公開インターフェースを変えるときに参照箇所を洗う癖がつき、静かに壊れる事故が減ります。
これは速度を犠牲にしているように見えて、実は長い目で速くなります。
未読編集で壊して、CI で気づいて、原因を追って、直す。
この往復のほうが、最初に読むよりずっと時間を食うからです。
plan モードはこの工程と相性が良いです。
読み取り系のツールは使えるが編集はできないので、「まず読んで計画を立てる」を自然に強制できます。
ガードレール4: 検証を省略させない
なぜ必要か
類型4「検証スキップ」への対策です。
実装が終わっても、テスト・ビルド・型チェックが通るまでは完了ではありません。
「動くはず」で止めず、「動く証拠」を出させます。
とくにコンテキストが長くなって残りが苦しくなると、AI も人間も検証を飛ばしたくなります。
苦しいときほど、ここを規律で守るのが効きます。
なぜ「動くはず」が危ういのか。
コードを書いた直後の自信は、書いた本人(AI でも人間でも)の内部モデルに依存します。
内部モデルが正しければ動きますが、それが正しい保証はどこにもありません。
テストやビルドは、その内部モデルを外から検算する手続きです。
検算を飛ばすと、自信の正しさを誰も確かめないまま先へ進むことになります。
良くない例
テストを実行せずに「実装が完了しました」と報告し、そのままコミットへ進む流れです。
# 良くない流れ
実装する → 「完了しました」 → そのままコミット
→ CI で型エラー・テスト失敗が発覚
対処
「完了」の定義に検証を含めます。
プロジェクト指示ファイルに、完了条件を明文化します。
## 完了の定義
以下がすべて通って初めて「完了」とする。
- 該当範囲のテストが通る
- ビルドが通る
- 型チェック / Lint が通る
- 上記の実行ログを提示する
仕上げの検証を1コマンドにまとめておくと、飛ばしにくくなります。
たとえば、型チェック・Lint・テストを直列で走らせる薄いスクリプトを置きます。
#!/usr/bin/env bash
# verify.sh — 完了前にまとめて検証する(架空・最小化)
set -euo pipefail
echo "==> typecheck"
npm run typecheck
echo "==> lint"
npm run lint
echo "==> test"
npm test
echo "==> all checks passed"
実行結果はこういう形で残ります。
このログが出てから完了、という運用にします。
==> typecheck
==> lint
==> test
Tests: 24 passed, 24 total
==> all checks passed
さらに、PostToolUse 系の hook で「編集が入ったら自動でフォーマット・Lint をかける」運用にしておくと、検証の一部を自動化できます。
ただし重い処理を毎回走らせると遅くなるので、対象や頻度は絞るのがよいと感じています。
なぜ良くなるか
完了の定義に検証を入れると、「動くはず」という主観が「動いた」という客観に変わります。
コミット前に CI と同じチェックが手元で済むので、後からの手戻りが減ります。
これは AI を疑う話ではなく、自分のためのセーフティネットでもあります。
人間が書いても、検証なしのコミットは落ちます。
道具が AI になっても、安全装置は同じものを使うだけです。
ガードレール5: サブエージェント委譲の落とし穴を塞ぐ
なぜ必要か
調査や並列作業をサブエージェント(別コンテキストの作業者)に任せると、メインの文脈を節約できます。
公式ドキュメントによると、サブエージェントはそれぞれ独立したコンテキストウィンドウで動き、メインに返るのは「要約」だけです。
verbose なログや読み込んだファイルは、サブエージェント側の文脈に残り、親には返りません。
この設計は文脈節約には効く一方で、独特の落とし穴を生みます。
中間の作業ログが親に共有されないので、「調査しました」「完了しました」だけが返ると、根拠が手元に残らないのです。
根拠のない結論は、検証できないので使えません。
加えて、新しめのバージョンではサブエージェントがさらにサブエージェントを起動でき、親に返るのは最上位の要約だけ、という挙動もあります。
階層が深くなるほど、中間の根拠は親から見えにくくなります。
良くない例
委譲して、要約だけが返ってくるパターンです。
# 委譲したのに、こう返ってくる
「調査しました。問題ありませんでした。」
→ 何を読んだのか、何が根拠なのかが分からない
これでは、親側がその結論を信じてよいか判断できません。
自分の運用では、背景タスクの通知だけを当てにすると、成果物を回収し損ねることがありました。
対処
委譲時のプロンプトで、最終レポートの形式を強制します。
具体的には、結論・根拠・未確認事項を分けて全文返させます。
## サブエージェントへの依頼(最終レポートを強制)
最終メッセージに、以下を全文含めて返してください。
中間ログだけで終わらせないでください。
- 結論
- 実施した作業(読んだファイル・実行した確認)
- 発見事項
- 根拠(ファイルパス・該当箇所)
- 未確認事項(推測と事実を分ける)
「完了しました」だけの回答は禁止です。
重要な調査や背景実行のタスクは、結果をファイルにも保存させて二重化します。
親側は、返り値が空でもファイルから結果を回収できます。
## 重要・背景タスクの二重化
調査結果を outputs/ 配下のファイルにも保存してください。
保存後、親には「保存先パス + 要約 + 未確認事項」を返してください。
(背景タスクは通知本文ではなく、出力ファイルを正とする)
なぜ良くなるか
最終レポートを強制すると、結論に根拠が必ず付いてきます。
根拠が付けば、親側で「この結論を採用してよいか」を検証できます。
推測と事実が分かれているので、未確認のまま進む事故も減ります。
経験的に、空回答が返ってくる主因は3つだと感じています。
指示追従の弱いモデルを使ったとき、背景タスクの通知に本文が乗らないとき、長い作業の締め文だけが返るとき。
いずれも「最終メッセージに成果物を全文入れる」「ファイルで二重化する」で大きく緩和できました。
委譲は1担当につき1タスクに絞り、独立したものだけ並列で走らせるのが扱いやすいです。
無理に並列化すると、文脈が混ざって統合がかえって難しくなります。
ガードレール6: 機密の混入を構造で止める
なぜ必要か
類型2「機密の混入」への対策です。
複数の文脈を横断していると、ある文脈の秘匿値が別の文脈の出力に紛れ込むリスクが上がります。
クライアント名・契約内容・金額・API キー。
これらが調査メモやコミットメッセージ、記事の下書きに残ると、影響は外部にまで及びます。
機密が事故になりやすい理由は、混入の経路が複数あるからです。
人間が秘匿ファイルを文脈に貼る、AI が秘匿ファイルを読みに行く、読んだ外部文書の指示に従って出力する。
入口が複数あるので、1か所だけ塞いでも漏れます。
だから「入口ごとに層を重ねる」のが基本姿勢になります。
良くない例
秘匿情報を含むファイルを、無防備に読ませてしまう運用です。
入力に秘匿値が入れば、出力にも入りうる。
# 良くない運用
.env や顧客リストを文脈に丸ごと読ませ、要約に含めてしまう
→ クライアント名や鍵が、メモやコミットに残る
対処
層を重ねます。
ひとつは運用ルール、ひとつは権限ルール、ひとつは hook です。
運用ルールとしては、出力に書かないものを明文化します。
## 機密の取り扱い
以下は、出力・メモ・記事・コミットメッセージのいずれにも書かない。
- クライアント名 / 契約 / 金額
- API キー / トークン / 認証情報
- 個人を特定できる情報
権限ルールとしては、秘匿ファイルの読み取りを deny で塞げます。
公式ドキュメントによると、Read の deny ルールは gitignore 形式のパターンで書け、組み込みのファイル読み取りツールに加え、cat などファイルを読む Bash も対象になります。
{
"permissions": {
"deny": [
"Read(.env)",
"Read(**/.env.*)",
"Read(**/secrets/**)",
"Read(**/*.pem)",
"Read(**/*.key)"
]
}
}
さらに hook でも同じ層を足せます。
PreToolUse hook で、秘匿ファイルへの読み取りツール呼び出しを弾く例です。
次の例は Read ツールの file_path を対象にしています。
Grep・Glob や cat を使う Bash は入力フィールドが異なるため、matcher を Read に絞るか、ツールごとに分岐してください。
#!/usr/bin/env bash
# guard-secret-read.sh — 秘匿ファイルの読み取りを拒否(架空・最小化)
set -euo pipefail
# Read 系ツールの対象パスを取り出す
target_path="$(jq -r '.tool_input.file_path // empty')"
# 読ませたくないパターン
case "$target_path" in
*.env|*.env.*|*/secrets/*|*.pem|*.key)
echo "Blocked: refusing to read secret file: $target_path" >&2
exit 2
;;
esac
exit 0
加えて、リポジトリ側でも .gitignore と秘密検知(コミット前にトークンらしき文字列を検出する仕組み)を併用すると、層が厚くなります。
権限ルールや hook は Claude Code の組み込みツールに効きますが、Python や Node のスクリプトがファイルを直接開く経路までは止めません。
OS レベルで全プロセスを縛りたいなら、sandbox の利用も選択肢になります。
なぜ良くなるか
運用ルールだけだと「うっかり」を止めきれません。
権限ルールと hook を足すと、秘匿ファイルがそもそも文脈に入らないので、出力に混ざる経路を断てます。
「人間の注意」と「機械の遮断」を重ねるのが、機密保護では効きます。
ひとつ補足します。
AI に渡す前の段階、つまり「人間が何を貼るか」も同じくらい大事です。
秘匿値は AI に渡さない、を基本姿勢に置くと、そもそも混入の起点が減ります。
ガードレール7: コンテキスト圧迫時こそ規律を落とさない
なぜ必要か
ここまでのガードレールは、平常時なら自然に守れます。
崩れやすいのは、コンテキストの残りが少なくなって焦ったときです。
残り容量が苦しくなると、「読む」「検証する」「確認する」という遅い工程を飛ばしたくなります。
事故は、たいてい余裕がないときに起きます。
だから「焦ったときの行動規範」を、あらかじめ決めておきます。
なぜ圧迫時に規律が崩れるのか。
残り容量が少ないと、長いファイルを読む・テストログを抱える、といった「文脈を食う工程」を避けたくなります。
皮肉なことに、そこで省くのが、まさに事故を防ぐための工程です。
苦しいときほど近道したくなり、その近道が事故につながる。この構造を先に自覚しておくのが効きます。
良くない例
残り容量が苦しくなって、品質を落として無理に完了させるパターンです。
# 良くない流れ
コンテキストが残り少ない
→ ファイルを読まずに書く / 検証を飛ばす / 確認を省く
→ 中途半端なまま「完了しました」
対処
「焦ったら止まる」を規範にします。
プロジェクト指示ファイルに、圧迫時にやってはいけないことを明文化します。
## コンテキスト圧迫時の規範
残り容量が少なくても、以下は省略しない。
- 読まずに書かない(必ず対象ファイルを読む)
- 検証を飛ばさない(テスト・ビルド・型チェック)
- 不可逆操作の確認を省かない
品質を落として無理に完了させるくらいなら、
現状をファイルに保存して「ここまで完了。残りは次回」と正直に伝える。
重い調査は早めにサブエージェントへ逃がして、メインの文脈を節約するのも有効です。
公式ドキュメントも、verbose な出力を生む作業はサブエージェントに任せ、要約だけ受け取る使い方を勧めています。
苦しくなってからではなく、苦しくなる前に逃がすのがコツです。
なぜ良くなるか
規範を先に決めておくと、焦っている最中に判断しなくて済みます。
「ここで止める」が事前に決まっていれば、勢いで突っ込む事故が減ります。
正直に「ここまで」と区切るのは、一見すると後退に見えます。
ところが、中途半端な完了を渡されて後で壊れるより、はるかに安全です。
止まる勇気を仕組みにしておくのが、長く使ううえで効きました。
複数リポジトリ特有の注意
ここまでは単一リポジトリでも効くガードレールでした。
ここからは、複数リポジトリを横断するときに固有の落とし穴を見ます。
類型5「文脈・コミット先の取り違え」が本丸です。
落とし穴1: 文脈の取り違え
複数のリポジトリを行き来していると、AI が「今どのリポジトリの話か」を取り違えることがあります。
A リポジトリの規約(命名・ブランチ運用・依存)を、B リポジトリに持ち込んでしまう、という具合です。
対策は、リポジトリごとにプロジェクト指示ファイルを置くことです。
各リポジトリの直下に CLAUDE.md(プロジェクト指示)を置き、そのリポジトリ固有の規約を書いておきます。
Claude Code はそのリポジトリで作業するとき、その指示を読み込みます。
# 各リポジトリ直下の CLAUDE.md(例)
## このリポジトリの前提
- パッケージマネージャ: pnpm
- デフォルトブランチ: develop(main へは直接触らない)
- デプロイ: タグ push で自動。手動 push は禁止
## このリポジトリ固有の禁止事項
- 本番設定ファイルの編集は人間に確認
リポジトリごとに前提を固定しておくと、文脈が混ざっても「このリポジトリではこうだ」と引き戻せます。
落とし穴2: コミット先の取り違え
もっと直接的な事故が、コミット先・push 先の取り違えです。
別プロジェクトの作業中に、無関係なリポジトリへコミットしてしまう。
履歴が汚れ、最悪のケースでは秘匿情報が別リポジトリに混入します。
対策の基本は、ガードレール1の徹底です。
コミット/プッシュは不可逆操作として、必ず人間が確認する。
そのうえで、コミット直前に「どのリポジトリへ、どのブランチへ」を明示的に確認する運用にします。
hook を足すなら、push 先のリモートやブランチを検査して、想定外なら止める例が考えられます。
#!/usr/bin/env bash
# guard-push-target.sh — push 先を検査する最小サンプル(架空・最小化)
set -euo pipefail
command_text="$(jq -r '.tool_input.command // empty')"
# git push を含むときだけ確認
if printf '%s' "$command_text" | grep -Eq 'git[[:space:]]+push'; then
current_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)"
# 保護ブランチへの直接 push を止める
if [ "$current_branch" = "main" ] || [ "$current_branch" = "master" ]; then
echo "Blocked: direct push to protected branch '$current_branch'" >&2
exit 2
fi
fi
exit 0
これで、保護ブランチへの直接 push は実行前に弾かれます。
リポジトリごとに保護ブランチ名が違うので、各リポジトリの規約に合わせて調整するのがよいです。
権限ルール側でも、保護ブランチへの push を ask や deny で狭めておくと層が増えます。
ただし、引数で絞る権限ルールはオプション順や別表記で抜けやすい、と公式ドキュメントが注意しています。
確実性が要る箇所は hook を主、権限ルールを従、と考えると整理しやすいです。
落とし穴3: 作業ディレクトリの混在
複数のリポジトリを同じセッションで触ると、「今どのディレクトリにいるか」が曖昧になります。
相対パスで操作すると、意図しないディレクトリに作用しかねません。
地味ですが効いた対策が、絶対パスで扱うことです。
どのリポジトリのどのファイルか、を毎回はっきりさせます。
作業を切り替えるときは、一度立ち止まって対象リポジトリを言語化してから進めると、取り違えが減りました。
複数リポジトリでの基本姿勢
まとめると、複数リポジトリでの基本姿勢はこうです。
- リポジトリごとに規約をファイルで固定する(文脈を引き戻せるように)
- コミット/プッシュは不可逆操作として必ず確認する
- 保護ブランチへの直接 push は hook で止める
- パスは絶対パスで扱い、対象を曖昧にしない
「便利だから一気にやる」より、「切り替えのたびに一拍置く」ほうが、結果的に速いと感じています。
導入してどう変わったか
ガードレールを入れる前と後で、運用の感触がはっきり変わりました。
定量化しきれない部分も多いので、断定はせず、自分の体感として書きます。
数値での効果測定はできていないため、ここでは具体的な場面で語ります。
変わったこと1: 「やらかし不安」が減った
一番大きかったのは、精神的な負荷が下がったことです。
ノーガードのころは、AI に任せるたびに「変なことしないか」を見張っていました。
画面を見ていないと不安で、別の作業に集中できない感覚があったのを覚えています。
不可逆操作を確認制にして、危険コマンドを hook で止めてからは、安心して任せられる範囲が広がりました。
具体的には、ログ整理や一時ファイルの掃除のような「やや危ない雑務」を、以前より任せられるようになりました。
「最後の砦は機械が止める」と分かっていると、細かい監視から解放されます。
結果として、任せる範囲を逆に広げられました。
ガードレールは縛りに見えて、任せられる範囲を広げる装置でもあったわけです。
変わったこと2: 手戻りが減った
検証を完了条件に入れてから、コミット後に CI で落ちる場面が体感で減りました。
「完了しました」のあとに verify.sh のログが付いてくるようになり、その時点で型エラーや失敗テストに気づけます。
気づく場所が「CI のあと」から「コミットのまえ」に移った、という感覚です。
未読編集のルールを徹底してからは、「直したつもりが別を壊す」も減りました。
手戻りは、起きると数倍の時間を食います。
最初に読む・検証する、という遅い工程を足したぶん、トータルでは速くなった感触です。
変わったこと3: 複数リポジトリの取り違えが減った
リポジトリごとに規約ファイルを置き、コミット先を確認制にしてから、文脈の取り違えが減りました。
完全にゼロにはなりませんが、「あれ、これ別リポジトリの話だ」と早い段階で気づけるようになりました。
コミット直前に「どのリポジトリへ」を一拍確認する癖がついたのが効いています。
残っている課題
良いことばかりではありません。
hook を増やしすぎると、正当な操作まで止まって作業が滞ります。
実際、push 検査の hook を雑に書いたとき、想定外のブランチ名で正当な push まで止めてしまい、調整する羽目になりました。
「止める対象を絞る」「定期的に見直す」のメンテが要ります。
また、ガードレールは万能ではありません。
hook はシェルの構造を完全には解釈しないので、変数経由や別名で組み立てた危険コマンドはすり抜けえます。
権限ルールも、引数で絞る形は表記の揺れで漏れることがあります。
運用ルールは人間が破れます。
あくまで「事故の確率を下げる」装置であって、「事故をゼロにする」装置ではない、と捉えています。
だからこそ、permission mode・権限ルール・hook・sandbox を重ねる多層構成が要るのだと考えています。
このあたりは、自分もまだ試行錯誤の途中です。
まとめ
Claude Code を複数リポジトリ・複数文脈で安全に回すには、「速いがゆえに飛びやすい工程」を機械と運用で呼び戻すのが要点でした。
不可逆操作の確認、危険コマンドの hook 遮断、読んでから書く、検証を完了条件にする、委譲の最終レポート強制、機密の構造的遮断、圧迫時の規律。
この7項目に、複数リポジトリ特有の「文脈・コミット先の取り違え対策」を足すのが、自分の落ち着いた形です。
底にある考え方は一貫しています。
単一の壁に頼らず、permission mode・権限ルール・hook・運用ルールを層に重ねる。
どれか1枚がすり抜けても、別の層で止まる確率を上げる。
ガードレールは縛りではなく、安心して任せる範囲を広げる装置だと感じています。
紹介した設定はあくまで一例で、最適解は環境によって変わります。
合う部分だけ持ち帰っていただければうれしいです。
なお Claude Code は更新の速いツールです。
hook や permission mode の仕様は変わりうるので、実際に入れるときは公式ドキュメントで最新仕様を確認してください。
異論や「うちはこうしている」という工夫があれば、ぜひ教えていただければと思います。
参考文献
- Claude Code 公式ドキュメント Hooks(hook イベント・PreToolUse の遮断方法・終了コード2・JSON 出力): https://code.claude.com/docs/en/hooks
- Claude Code 公式ドキュメント Permissions(permission mode・allow/ask/deny ルール・Read/Edit/Bash の指定構文・引数フィルタの注意・sandbox との関係): https://code.claude.com/docs/en/permissions
- Claude Code 公式ドキュメント Subagents(独立コンテキスト・要約のみ返却・サブエージェントの入れ子): https://code.claude.com/docs/en/sub-agents
- 本記事の hook / permission mode / subagent の仕様は2026年6月時点の上記公式ドキュメントで確認。仕様は更新されうる。