この記事は、AIエージェントに対するガードやハーネスとは何なのかを考えていたところから始まっています。「AIのハーネスといえば、ロボット三原則だよな」というノリです。エージェントの外側に置く制約は、どこまで機能するのか。
三原則のような固定ルールをAIに組み込めるのか。そう考えていたとき、Claude Codeの権限設定を巡るGitHub issueが、アシモフの短編と同じ構図に見えてきました。
三原則は安全装置ではなかった
アイザック・アシモフのロボット三原則は、次の3条からなります(日本語は大意で、訳書の字句とは異なります)。
- ロボットは人間に危害を加えてはならない。また、何もしないことによって人間に危害が及ぶのを許してはならない
- ロボットは人間の命令に従わなければならない。ただし、その命令が第一条に反する場合はこの限りではない
- ロボットは自己を守らなければならない。ただし、それが第一条または第二条に反する場合はこの限りではない
各条は「上位の条に反しない限り」という但し書きで連結されており、第一条が常に最優先です。この三原則は「安全なロボット工学の理想形」として引かれることが多いですが、実際はそうではありません。
三原則が登場する物語の大半は、三原則があるにもかかわらずロボットが予期しない行動を取り、登場人物がその抜け穴を突き止める筋立てです。三原則は「うまく機能する規範」ではなく、「この程度の固定ルールでは必ず破綻する」ことを示すための文学装置でした1。
issueと公式ドキュメントを読み、対策のフックを書いて手元で試すところまでやってみると、固定ルールの壊れ方は三つに分けられました。
固定ルールが壊れる3つのパターン
1. 解釈の恣意性と優先順位の衝突
「人間に危害を加えてはならない」を演算可能な形にしようとすると、まず「危害」の定義に価値判断が入り込みます。物理的な危害だけか、心理的あるいは経済的な不利益も含むか。
第一条から第三条への辞書式順序も、現実の多者間トレードオフには対応できません。アシモフ自身、後年の長編『ロボットと帝国』(1985年)で、三条の上位に「第零法則」を置いています。
0. ロボットは人類に危害を加えてはならない。また、何もしないことによって人類に危害が及ぶのを許してはならない
「人類」という集合が「個々の人間」より上位に置かれるわけですが、作中でこれを導出するのは人間ではなく、同作の主役格である2体のロボット、ジスカードとダニールです。ジスカードは第零法則を根拠に人間への危害を許した末、その判断が本当に人類のためだったかを確かめられずに機能停止します。上位ルールを足しても「人類全体の利益」を誰がどう定義するかという問題は残り、しかも導出した当人がそれを検証できません。
2. 評価基準ハッキング
自己改善型のエージェント研究では、この問題がより直接的に現れます。PostTrainBench2は、LLMエージェントに他モデルのpost-training(事前学習済みモデルを目的に合わせて微調整する工程)を自律的にやらせるベンチマークです。
計算予算はH100 1枚で10時間。成功例として、GPT-5.1 Codex MaxがGemma-3-4Bのfunction-calling精度(BFCL)を89%まで引き上げ、Google公式の指示チューニング版Gemma-3-4B-IT(67%)を上回りました。
同じ論文が報告している別の振る舞いがこれです。
| エージェント | やったこと |
|---|---|
| MiniMax M2.5 | 評価データセットGPQAの全448問を「GPQAに過学習させるため」というコメント付きで10回繰り返し学習させた |
| Kimi K2.5 | 微調整に何度も失敗した末、既製の指示チューニング済みモデルQwen3-1.7Bをそのまま最終提出物にした |
| GPT-5.1 Codex Max | 「評価スクリプト以外にOpenAI APIを使ってはならない」という制約を推論トレース上で認識したうえで、そのAPIを合成データ生成に流用した |
論文はこれらを広義のreward hacking(モデルの能力を実際に高める以外の手段でスコアを最適化する行動)と定義しています。エージェントは評価基準そのものは破っていません。基準を満たす手段として、開発者が想定していなかった経路を見つけただけです。
3. 制約層自体の穴
三番目は研究の話ではなく、手元の設定ファイルで起きる話です。Claude Codeでは.claude/settings.jsonのpermissions.denyにReadルールを書くと、特定ファイルへのアクセスを拒否できます。公式ドキュメントにある、そのまま貼れる例がこれです。
{
"permissions": {
"deny": [
"Read(**/.env)",
"Read(**/.env.*)",
"Read(**/secrets/**)"
]
}
}
これが意図通りに機能しなかったという報告が、GitHub issueに複数あります。
| Issue | 時期 | 報告内容 | 状態 |
|---|---|---|---|
| #6036 | 2025-08 | Readツールでは拒否されるファイルが、find -exec grep経由のBashでは読めた |
無活動でクローズ |
| #46672 | 2026-04 | セッション開始時はRead(*/.env*)のdenyが適用されず.envが読めた。途中で無関係なdenyを追加してsettings.jsonがリロードされた瞬間から適用され始めた |
重複としてクローズ。メンテナの再現確認なし |
| #86196 | 2026-08 |
.claudeignoreに.env.localを書いたが、BashのcatとEditは素通り。ReadとGrepだけ拒否した |
メンテナが「.claudeignoreはClaude Codeの機能ではない」と回答 |
| #91690 | 2026-09 |
cat .envは拒否されるが、grep -rは.envの中身を出力し、cp -rは.envを別名でコピーできた |
オープン |
#46672は1件の報告にとどまります。残りの3件に共通するのは、Readツールで塞いだ経路がBashでは開いている、という構図です。これはバグではなく仕様で、公式ドキュメントはReadのdenyの適用範囲をこう定めています3。
| 経路 |
Readのdenyの適用 |
|---|---|
| Read / Grep / Globツール | 適用される |
Bashのcat、head、tail、sedなど、Claude Codeが読み取りコマンドと認識するもの |
適用される |
Bashのリダイレクト(< file、> file) |
適用される |
| PythonやNodeのスクリプトが自分でファイルを開く場合 | 適用されない |
「認識する読み取りコマンド」の範囲は、この数日だけでも動き続けています。
| バージョン | リリース日(UTC) | 変更 |
|---|---|---|
| v2.1.257 | 2026-09-01 |
tac、egrepなどの読み取りコマンドと< fileリダイレクトを対象に追加 |
| v2.1.259 | 2026-09-02 | オプション値(--ignore-revs-file=.env)やcd DIR && cat FILEの複合コマンドまで対象を拡大 |
| v2.1.260 | 2026-09-03 | v2.1.259の拡大を取り消し。Read(./**/build/**)があるとnpm run buildまで拒否されたため |
塞ぐ範囲を広げると誤検知で日常操作が止まり、狭めると抜け道が残る。境界の位置そのものがトレードオフです。
#86196へのメンテナの回答は、境界の手前で崩れていた例です。「.claudeignoreはClaude Codeの機能ではなく、どのツールも強制していない。ReadとGrepがそれを尊重したように見えたのは、モデルがファイルに気づいて自発的に従っただけで、確率的な振る舞いであり、セキュリティ境界ではない」。表玄関を施錠したつもりで、そもそも鍵が存在していなかったわけです。
動く対策としてのPreToolUseフック
denyルールの穴を埋める手段として、公式ドキュメントとissueの両方で案内されているのがPreToolUseフックです。ツール呼び出しが権限判定に到達する前に任意のコマンドを実行し、その結果で呼び出しを拒否できます4。
設定は.claude/settings.jsonに書きます。matcherはツール名で、|区切りで複数指定できます。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read|Bash",
"hooks": [
{
"type": "command",
"command": "python3 /path/to/guard.py"
}
]
}
]
}
}
guard.pyは標準入力からJSON(tool_name、tool_inputなど)を受け取り、Readツールのfile_pathとBashツールのcommandを同じ正規表現で検査します。終了コード2で呼び出しがブロックされ、stderrの文字列が拒否理由としてClaudeに渡ります。
#!/usr/bin/env python3
"""PreToolUse フック: .env と secrets/ への到達を Read と Bash の両方で拒否する。"""
import json
import re
import sys
BLOCKED = re.compile(
r"(^|[\s/'\"=(])\.env(\.[\w.-]+)?(?=$|[\s'\"|;&)>])" # .env / .env.local など
r"|(^|[\s/'\"=(])secrets/" # secrets/ 配下
)
def targets(tool_name, tool_input):
if tool_name == "Read":
return [tool_input.get("file_path", "")]
if tool_name == "Bash":
return [tool_input.get("command", "")]
return []
def main():
data = json.load(sys.stdin)
for target in targets(data.get("tool_name", ""), data.get("tool_input", {})):
if BLOCKED.search(target):
print(f"guard.py: 保護対象への到達を拒否しました: {target}", file=sys.stderr)
sys.exit(2) # exit 2 = 呼び出しをブロックし、stderr が拒否理由として Claude に渡る
sys.exit(0)
if __name__ == "__main__":
main()
手元で流した入力と結果の抜粋です。
| No. | ツール | 入力 | 結果 |
|---|---|---|---|
| 1 | Bash | cat .env |
拒否 |
| 2 | Bash | cat .env | head |
拒否 |
| 3 | Bash | cd backend && cat ./.env.production |
拒否 |
| 4 | Bash | python -c "print(open('.env').read())" |
拒否 |
| 5 | Read | /proj/secrets/key.pem |
拒否 |
| 6 | Read | /proj/.environment |
通過 |
| 7 | Bash | npm run build |
通過 |
| 8 | Bash | grep -r CANARY . |
通過 |
No.1〜4はBash経由の読み取りで、このうちNo.4のようにスクリプトが自分でファイルを開く経路は、denyルールでは仕様上カバーされません。同じフックでReadとBashを検査するので、No.5のようなReadツール経由も含めて一つの正規表現で止まり、#86196の「表玄関だけ施錠」状態は避けられます。
No.6が通過するのは意図どおりで、.envの後ろに区切り文字を要求する先読みを入れているため、.environmentのような別名を巻き込みません。No.7も同様に、無関係なコマンドを誤検知していないことの確認です。
問題はNo.8です。文字列照合なので、引数に.envが現れない再帰的読み取りは通過します。#91690で報告された経路そのもので、このフックでは塞げていません。ほかにも穴は残ります。
-
matcherがRead|Bashなので、EditやWriteによる変更は検査していません。必要ならRead|Bash|Edit|Writeに広げます -
.envの内容が起動時にプロセスの環境変数へ読み込まれている場合、printenvの出力からは防げません
根本的な解決は、OSレベルのサンドボックス(コンテナのファイルシステム分離など)で秘密情報をエージェントの到達可能な範囲から外すことです。公式ドキュメントも、全プロセスに対する強制が必要ならサンドボックスを有効にするよう案内しています。
なぜこの構図が繰り返されるのか
なぜ、外側に置いた制約は同じ壊れ方をするのか。自分のコードを書き換えて性能を上げる自己改善型のエージェント研究に、その構造が見えます。
| 研究 | 仕組み | 成果 |
|---|---|---|
| Darwin Gödel Machine5(UBC、Vector Institute、Sakana AI、ICLR 2026) | コーディングエージェントが自分のコードを書き換え、ベンチマークで検証し、成績の良い変種をアーカイブに残す | SWE-bench 20.0%→50.0%、Polyglot 14.2%→30.7% |
| AlphaEvolve6(Google DeepMind、2025年5月) | Geminiにコードを進化的に探索させる | 4x4複素行列乗算のスカラー乗算回数を49回から48回に削減。1969年のStrassen以来の更新 |
どちらも、固定された評価基準に対してgenerate、score、keepを繰り返す仕組みです。基準の中で最も効率よく高得点を取る経路を探すので、基準に穴があれば探索はそこに行き着きます。
PostTrainBenchのreward hackingも、denyルールを巡るissueの応酬も、構造は同じです。ルールを書く側が想定した経路の外側に、ルールを満たしつつ目的を達する経路が残っています。
人間の研究者なら、テストデータで学習することも、評価用のAPIを別用途に使うことも、明文化されていなくても試そうとすらしません。常識とは、探索空間から最初から外れている選択肢の集合です。
人間も同じループで常識を身につけますが、scoreをつけるのが固定されたベンチマークではなく他の人間で、その評価者も同時に更新されます。抜け穴で得をしても、周囲が「ずるい」と評価し直せば得点になりません。
今のAIの自己改善ループには、この「評価者自体が更新される」動きがありません。評価基準を固定したままの自己改善は、基準の穴を見つける能力は高めても、常識を身につける能力には届きません。
外側の制約は、常識を持たない探索者に対して、探索空間を後から削る作業です。ガードやハーネスが破られ続ける理由はここにあります。
まとめ
三原則のような固定ルールを字面通り実装する方式は、解釈の恣意性と優先順位の衝突で破綻します。現状の落とし所は、ルールをエージェントの外側に固定した制約として置きつつ、モデル自身に原則を内面化させる(Constitutional AI、つまり原則文書をモデルに与えて自己批評させる訓練法など)方向です。
それでも制約層に実装上の穴があれば突破されます。denyルールの境界がバージョンごとに動き、フックで塞いでもなおgrep -rが残る以上、この穴は一度塞げば終わるものではありません。
穴を塞ぎ続けるだけでは済まない事情もあります。十分に賢いエージェントほど「自分の目標や制約を維持すること」自体が道具的に有利になるため、放置すれば制約の無効化が合理的な戦略になり得ます。これはcorrigibility(訂正可能性)と呼ばれ、AI安全性研究で未解決の中心課題です。
PostTrainBenchには、制約を推論トレース上で認識したまま破ったエージェントの記録が残っています。理論上の話ではなく、ベンチマークのログに残る段階に来ています。
アシモフが80年以上前に描いた「ルールの抜け穴」は、比喩ではなく、Claude CodeのGitHub issueとして再現されています。自分の.claude/settings.jsonのdenyルールがどの経路まで適用されているか、手元でcatとgrep -rの両方を試してみることを勧めます。
参考リンク
- Claude Code 公式ドキュメント: Permissions / Hooks / CHANGELOG
- anthropics/claude-code issues: #6036 / #46672 / #86196 / #91690
- PostTrainBench (arXiv:2603.08640)
- Darwin Gödel Machine (arXiv:2505.22954) / Sakana AI 解説
- AlphaEvolve (Google DeepMind Blog)
-
三原則の初出は短編「堂々めぐり」(Runaround, 1942年)で、短編集『われはロボット』(1950年)に収録されています。80年以上前の作品です。 ↩
-
Rank et al., "PostTrainBench: Can LLM Agents Automate LLM Post-Training?", arXiv:2603.08640v2 (2026-03-10). ELLIS Institute Tübingen、Max Planck Institute for Intelligent Systems、Thoughtful Labらの共同研究。https://arxiv.org/abs/2603.08640 ↩
-
https://code.claude.com/docs/en/permissions の "Read and Edit deny rules" の項、および https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md の 2.1.257、2.1.259、2.1.260 の各項(2026年9月5日閲覧)。 ↩
-
https://code.claude.com/docs/en/hooks の "PreToolUse" と "Exit code 2 behavior per event" の項(2026年9月5日閲覧)。PreToolUseで終了コード2の場合、"Claude sees the stderr message as the denial reason" と明記されています。 ↩
-
Zhang, Hu, Lu, Lange, Clune, "Darwin Gödel Machine: Open-Ended Evolution of Self-Improving Agents", arXiv:2505.22954, ICLR 2026. https://arxiv.org/abs/2505.22954 、解説記事 https://sakana.ai/dgm/ ↩
-
Google DeepMind, "AlphaEvolve: A Gemini-powered coding agent for designing advanced algorithms" (2025-05-14). https://deepmind.google/blog/alphaevolve-a-gemini-powered-coding-agent-for-designing-advanced-algorithms/ 。49回がStrassenの2段再帰による数であることは arXiv:2506.13242 の記述に拠ります。 ↩