
Claude Code v2.1.324〜v2.1.325 の主要アップデート - サンドボックス内 Bash の権限フロー変更と Bash 権限チェックの修正
クラウド事業統括本部の石川です。Claude Code の v2.1.324 〜 v2.1.325(2026-11-13)のアップデートをまとめてご紹介します。v2.1.324 は npm に公開されていますが CHANGELOG に記載がないため、本記事では v2.1.325 の変更内容を紹介します。また、前回の記事で 404 だった Code Mode のドキュメントが公開されていたため、実験的機能 「Code Mode」 もあわせて検証ました。
前回のアップデート記事はこちらです。
アップデートサマリー
対象は 2 バージョン(v2.1.324 〜 v2.1.325、2026-11-13)で、CHANGELOG の記載は v2.1.325 の 139 件です。本記事の分類では、修正 68 件、セキュリティ 31 件、開発者体験 13 件、破壊的変更 9 件、改善 9 件、新機能 6 件、パフォーマンス 2 件、非推奨 1 件です。権限チェックとサンドボックスに関する項目が多く、確認の要否が変わる既定の挙動の変更も含まれています。
注目のアップデート
サンドボックス内の Bash が既定で通常の権限フローに従うよう変更(v2.1.325)
サンドボックス内の Bash コマンドが、既定で通常の権限フローに従うようになりました。従来どおり確認なしで実行するには sandbox.autoAllowBashIfSandboxed: true を設定します。管理設定でサンドボックスを有効にしている場合は、最も優先度の高い管理設定ソースに sandbox.autoAllowBashIfSandboxed: true を設定します。
サンドボックスによる自動承認に頼っていた方は、アップデート後に確認や権限拒否が発生する可能性があるため、設定を確認しておくのがよいと見ています。
Bash の権限チェックの修正(v2.1.325)
Bash の権限チェックについて、次の 3 件が修正されました。
- 一部の Bash コマンドに deny・ask ルールが適用されない問題
- 一部の複合コマンドが確認なしで実行できた問題(確認を求めるように変更)
- Bash の allow ルールで、ルールに書いたものと異なるプログラムを実行できるケース
Bash の allow・ask・deny ルールで操作を制限している方に関わる修正のため、早めにアップデートしておくのがよいと感じます。
allowedProviders が許可していない Amazon Bedrock へのトークンカウントの送信を修正(v2.1.325)
管理設定 allowedProviders が Amazon Bedrock を許可していないのに、Amazon Bedrock でのトークンカウントで会話を Bedrock に送信していた問題が修正されました。
allowedProviders で Amazon Bedrock を許可していない組織の管理者の方は、トークンカウント時に会話が Bedrock へ送信される不具合の修正のため、早めの展開を検討する価値があると感じます。
zstd 圧縮レスポンスのデコード処理のセキュリティ修正(v2.1.325)
zstd 圧縮されたレスポンスのデコード処理にあったセキュリティ上の問題が修正されました。
CHANGELOG には詳細の記載がありませんが、セキュリティ上の修正のため早めにアップデートしておくのがよいと感じます。
対象バージョンと期間
| バージョン | 公開日 | CHANGELOG の記載 |
|---|---|---|
| v2.1.324 | 2026-11-13 | なし |
| v2.1.325 | 2026-11-13 | 139 件 |
新機能
- 組織のポリシーでリクエストがブロックされたときのエラーの下に、続行方法(
/rewindでメッセージを変更する、または/clearを実行する)を示す行を追加(v2.1.325) - サンドボックス内の
git checkout・git stash・autostash 付きの pull が.claude内などの保護対象ファイルを更新できないとき、Claude に注記を渡すよう追加(v2.1.325) - Claude が Jupyter ノートブックを読み取り・編集した後、VS Code などの統合ターミナルで、ノートブックのセルの再実行に関するスピナーのヒントを表示(v2.1.325)
VS Code 拡張機能の新機能は「その他のプラットフォーム」で紹介します。
改善
- 出力トークン上限で打ち切られたサブエージェントに、回答の末尾だけを返させるのではなく、回答全体をより簡潔に改めて求めるよう改善(v2.1.325)
CLAUDE_CODE_MAX_CONTEXT_TOKENSで、ゲートウェイのclaude-sonnet-latestのようにclaude-で始まる未知のモデル ID のコンテキストウィンドウも設定するよう変更(v2.1.325)claude remote-controlが自分で停止したときは終了ステータス 4(単一セッションの実行は 0 のまま)、登録できないときは 69 で終了するよう改善し、サービスマネージャーで再起動できるように(v2.1.325)- MCP タスクがユーザーの回答を待っていることを Claude に伝え、Claude が
/tasksを案内できるよう改善(v2.1.325) - バックグラウンド MCP タスクの質問で Esc を「後で回答」に変更。サーバーには何も送らず、質問は
/tasksに残ります(v2.1.325) - キャッシュされたアカウントデータがない
claude -pが、最初のリクエストの前にそのデータを待つよう変更。クリーンな環境での初回実行は、開始が数秒遅くなることがあります(v2.1.325) /cost、$.session.usage()、--max-budget-usd、コストとトークンのメトリクスで、mod の$.model.completeと$.model.classifyの呼び出しも集計するよう変更(v2.1.325)- バックグラウンドタスクやスケジュールタスクが始めたターンが 8 回続いた後、ターミナルのプロンプト候補の更新頻度を下げ、キーを押すかターミナルに戻るまで使用量を抑えるよう変更(v2.1.325)
- プラグインの MCP バンドル(
.mcpb、.dxt)の展開先を、プラグインのフォルダ内の.mcpb-cacheからプラグインディレクトリの配下に変更し、読み取り専用のプラグインフォルダでも読み込めるように(v2.1.325) - tmux と iTerm2 のチームメイト、エージェントビューで ← を押して開いた新しいセッションを、シェルで最後に
cdした場所ではなくセッションのフォルダで開始するよう変更(v2.1.325) - このほか、キーレスの Console サインインでの起動、プライベートなプラグインマーケットプレイスの
HTTPS authentication failedメッセージ、claude --desktopのサインインエラー、macOS と Windows での/voiceの「No audio detected」メッセージ、同梱の dataviz スキルの助言などが改善されています(v2.1.325)
セキュリティ
注目のアップデートで紹介した項目のほかに、次の修正と変更があります。
- 権限ルール
Read(./.env)のような相対パスの deny・ask 権限ルールが、cdの後にプロジェクトを対象にしなくなる問題を修正。追加ディレクトリにも適用されます(v2.1.325)mcp__plugin_*_で始まる deny・ask 権限ルールが、大文字小文字の異なる表記のプラグインの MCP サーバーやツールに一致しない問題を修正(v2.1.325)- セルフホストランナーと
claude remote-controlのセッションで、Claude Code の再起動後に届いた権限の承認に deny ルールが適用されない問題を修正(v2.1.325) - リポジトリの特定のサブフォルダで Claude Code を起動したとき、コミットされた
.claude/settings.local.jsonがユーザー自身のものとして扱われることがある問題を修正(v2.1.325) - スラッシュコマンドの
allowed-toolsによる許可が、そのターン中やターン後に起動したバックグラウンドエージェントにも適用されていた問題を修正(v2.1.325) - 管理設定
strictPluginOnlyCustomizationによるエージェントとスキルのロックが、実行中のセッションに反映されない問題を修正(v2.1.325) - 一部のクラウドセッションで Artifact ツールに対する deny・ask の権限ルールとフックが適用されない問題と、クラウドセッションでプラグインの読み込み中にコネクタのツールの承認プロンプトが自動で応答される問題を修正(v2.1.325)
- サンドボックス
- サンドボックス内のコマンドがネイティブアップデーターのダウンロードフォルダに書き込めることがある問題を修正(v2.1.325)
- Bash サンドボックスがワークツリーの設定とフック、リンクされたワークツリーの共有 git フォルダを完全には保護していなかったケースを修正(v2.1.325)
- 追加ディレクトリ内の
.claudeの設定・スキル・エージェント・コマンド・フックへのサンドボックス内からの書き込みを、起動フォルダと同様に拒否するよう変更(v2.1.325)
- 認証情報・個人情報
- Claude Code のサンドボックスが自身のために起動するプログラム(ネットワークブリッジなど)を、Claude Code のサインイントークンと API キーを環境変数に含めずに実行するよう変更(v2.1.325)
- ripgrep、ローカルの git コマンド、サンドボックスランタイムのヘルパープログラムを、mTLS クライアントキーのパスフレーズを環境変数に含めずに実行するよう変更(v2.1.325)
- アカウントのメールアドレスの確認を拡大し、コマンドが実行するスクリプト、ファイル書き込み、Web フェッチ、MCP 呼び出しでも、ユーザーのメールアドレスをクライアントの識別情報として使うことを拒否するよう改善(v2.1.325)
/feedbackと/bugが他のセッションをアップロードするのは、サインイン中のアカウントまたは組織で記録されたセッションに限るよう修正(v2.1.325)
- Artifact
- allow ルールを設定しているときや以前にアップロードを承認していたときに、一部のサブエージェントが組織外の人も閲覧できる Artifact を確認なしで変更したり、ファイルをアップロードしたりする問題を修正(v2.1.325)
- Artifact で「don't ask again」を選んでいたときに、組織外の人も閲覧できるページの公開で確認が省略されることがある問題を修正(v2.1.325)
- auto mode が組織外の人も閲覧できる Artifact への最初の変更だけを判定していた問題を修正。承認はその変更だけに適用されます(v2.1.325)
- このほか、プラグインのインストール警告などでのプラグインがコードを実行できる旨の表示、ワークツリー分離されたセッションでの
git bisect runなどの拒否、プラグインのフォルダ内の安全でないパスのスキップ、/loginでのプライバシー設定の変更前の確認などの変更があります(v2.1.325)
修正
安定性・操作性に関わる修正を中心に抜粋します。
- モデル設定が
opusplanまたはhaikuのとき、独自のモデルを持つサブエージェントが plan モード用のモデルに切り替わる問題と、メインセッションと異なるモデルで動くサブエージェントが、ファイル読み取りと MCP の結果のサイズをメインセッションのモデル基準で決めていた問題を修正(v2.1.325) - 多くのサードパーティゲートウェイと Foundry のセッションで、コンパクションされずに毎ターンコンテキスト長エラーで停止する問題を修正(v2.1.325)
- Claude Code が正常終了しなかった後に再開したセッションで、直近のターンが欠落することがある問題を修正(v2.1.325)
- 作業ディレクトリが削除された後、シェルコマンドが起動ディレクトリで実行されていた問題を修正。コマンドはメッセージ付きで失敗し、再実行できます(v2.1.325)
plansDirectoryフォルダに書き込めないとき、承認済みのプランの編集が失われる問題を修正。既定のプランフォルダに保存し、保存先と理由を警告で表示します(v2.1.325)- 保存済みの
sonnet[1m]モデル設定が、新規インストール時に API が受け付けないモデル ID に書き換えられる問題を修正(v2.1.325) - 短時間で終わる
claude auth statusなどのコマンドが Console プロファイルにロックを残し、次のセッションが約 1 分止まる問題を修正(v2.1.325) - claude.ai のログインで開始したバックグラウンドセッションが、バックグラウンドサービスを起動したシェルの API キーやトークンを使用し、そちらに課金されていた問題を修正。あわせて、バックグラウンドでセッションが別のサインインに切り替わるケースも修正されました(v2.1.325)
HOME環境変数なしで Claude Code を起動したとき、AWS のcredential_processヘルパーと Google Cloud のデフォルト認証情報が失敗する問題を修正(v2.1.325)- Agent SDK の
applyFlagSettings()の呼び出しに無効な値が 1 つあると、それまでの呼び出しの設定がすべて通知なしに破棄される問題を修正。該当する呼び出しはエラーで拒否されます(v2.1.325) - 一時的に拒否されたトークン更新の後に MCP サーバーが「needs authentication」のままになり
mcp_toolフックがスキップされる問題と、ツール呼び出しで MCP サーバーが再接続された後も、SDK ホストからの MCP ツール呼び出しが「Not connected」を返す問題を修正(v2.1.325) - このほか、Code Mode、OpenTelemetry の
user_promptイベント、MCP タスク、Windows の/resume、Linux のサンドボックス内の Bash など、多数の細かな不具合が修正されています。
破壊的変更・非推奨
サンドボックス内の Bash の権限フロー(v2.1.325)
サンドボックス内の Bash コマンドは、既定で通常の権限フローに従います。
変更前(〜v2.1.323): 次の設定で、サンドボックス内の Bash コマンドは確認なしで実行されていました。
{
"sandbox": {
"enabled": true
}
}
変更後(v2.1.325〜): 同じ設定では通常の権限フローに従います。従来どおり確認なしで実行するには、autoAllowBashIfSandboxed を追加します。
{
"sandbox": {
"enabled": true,
"autoAllowBashIfSandboxed": true
}
}
管理設定でサンドボックスを有効にしている場合は、最も優先度の高い管理設定ソースにこの設定を入れます。
公式ドキュメント(settings-reference)によると、true のときもサンドボックス内で実行できないコマンドは通常の権限フローに従い、deny ルールと Bash(git push *) のような内容を指定した ask ルールは適用されます。false のときは、allow ルールと権限モードで判定されます。なお、本記事の執筆時点(2026-11-14)の公式ドキュメント(sandboxing、settings-reference)は、autoAllowBashIfSandboxed の既定値を true と記載しており、CHANGELOG の記載と一致していません。
スケジュールタスクと /loop でのスキルの実行(v2.1.325)
スケジュールタスクと /loop で、allowed-tools またはフックを宣言するスキルを実行する前に確認を求めるようになりました。
変更前(〜v2.1.323): スケジュールタスクと /loop はこれらのスキルの実行前に確認を求めませんでした。
(スキルの実行前の確認なし)
変更後(v2.1.325〜): 無人で実行するには、permissions.allow に Skill(name) を追加します。以下は設定例です(name は対象のスキル名です)。
{
"permissions": {
"allow": ["Skill(name)"]
}
}
破壊的変更・非推奨の一覧
| 対象 | 変更前 | 変更後・必要な対応 |
|---|---|---|
| サンドボックス内の Bash | 確認なしで実行 | 通常の権限フローに従う。確認なしで実行するには sandbox.autoAllowBashIfSandboxed: true |
| 管理設定によるサンドボックスの有効化 | 確認なしで実行 | 最も優先度の高い管理設定ソースに sandbox.autoAllowBashIfSandboxed: true |
| プロジェクトのものでも信頼済みでもないリポジトリでの読み取り専用の git コマンド | 確認なしで実行 | 実行前に確認を求める。確認なしで実行するには、additionalDirectories 設定ではなく --add-dir でリポジトリを指定 |
スケジュールタスクと /loop での、allowed-tools またはフックを宣言するスキル |
確認なしで実行 | 実行前に確認を求める。無人で実行するには permissions.allow に Skill(name) |
| Desktop・IDE・SDK のホストの「Always allow」 | ルール単体を提示 | ルール単体の提示は、保存するとそのコマンドを再度許可できる場合に限る。これに依存していたスケジュールタスクは、ユーザーの操作待ちになることがある |
--plugin-dir のプラグイン内の .claude/skills |
上位のフォルダで開始したセッションから読み込む | 上位のフォルダで開始したセッションからは読み込まない。プラグインのフォルダで Claude Code を起動する |
コンテナにマウントしたワークツリー・サブモジュールでの -p と SDK の実行 |
.claude/settings.local.json の allow ルールを信頼前から適用 |
フォルダが信頼済みになってから適用 |
マーケットプレイスの ref |
— | ブランチやタグ上の任意のコミットの完全な ID を受け付ける。この指定は v2.1.325 より前のクライアントでクローンできず、そのマーケットプレイスを読み込めない |
ALL_PROXY だけを設定した環境でのプラグインマーケットプレイスとプラグインのダウンロード |
— | CONNECT トンネルを使う。CONNECT を拒否するプロキシでは失敗する |
pluginDigests(非推奨) |
— | 非推奨になり、無視される。管理設定が宣言・有効化し prependPlugins または appendPlugins に列挙したリモートマーケットプレイスのプラグインは、組織のプラグインとして扱われる |
一覧はすべて v2.1.325 の変更です。
その他のプラットフォーム
VS Code 拡張機能
- ターミナルでプロンプト候補が表示される場面で、拡張機能でもプロンプト候補を表示。Tab で採用し、Claude Code の settings.json で
promptSuggestionEnabled: falseにすると無効になります(v2.1.325) - API キーを作成しない Console サインインを追加(v2.1.325)
- SSH・WSL・コンテナのウィンドウで、パネルから MCP サーバーにサインインするときにリダイレクト URL を貼り付ける欄を追加(v2.1.325)
- このほか、モデル選択のチェックマーク、effort の選択肢、サインイン画面、MCP ツールの権限プロンプト、組織の設定を読み込めなかったときのメッセージの修正と、信頼されていないフォルダの注記やエラーメッセージの改善があります(v2.1.325)
Claude Code on the web・クラウドセッション
- 新しいクラウドセッションで、ツール検索で読み込んだ MCP ツールの説明が 4,096 文字で切られていた問題を修正。他の Claude Code セッションと同じ 16,384 文字になりました(v2.1.325)
- Claude Code on the web、クラウドのスケジュールタスク、Cowork のクラウドセッションを、
sandbox.failIfUnavailableがあっても開始するよう変更。ホスト型コンテナにはコマンドのサンドボックスがありません(v2.1.325)
Claude Desktop・Claude Tag
- Claude Desktop のサードパーティ向けデプロイで、トークンの更新が終わるまで「Stopping」のままになる問題を修正。Claude Desktop が本バージョンを取り込んだ時点で反映されます(v2.1.325)
- Claude Tag で、組織が管理していないワークスペースで投稿者の Claude のサインインが期限切れのとき、Slack に貼った Artifact のリンクにカードが付かない問題を修正。プレーンなカードが表示されます(v2.1.325)
Code Mode を試してみた
前回の記事では、Code Mode のドキュメントが 2026-11-13 時点で 404 だったため手順を確認できませんでした。2026-11-14 に確認したところ公開されていたため、Code Mode を有効にして、同じタスクを Code Mode なし・ありで実行して比べました。v2.1.325 にも、Code Mode のスクリプトが EnterWorktree で入ったワークツリーを無視する問題など、Code Mode に関する修正が 3 件含まれています。
検証環境は macOS(Darwin 25.6.0、arm64)、Claude Code v2.1.325 です。
Code Mode の仕組み
ドキュメントによると、Code Mode なしの Claude は Bash・Read・Edit などのツール呼び出しを直接要求し、その結果を読んでから次の操作を決めます。Code Mode では、Claude がツール呼び出しを行う JavaScript を書き、Claude Code がそのコードをセッション内で実行します。1 つのコードの中で、コマンドを並列に実行したり、ある呼び出しの結果を次の呼び出しに使ったり、出力のうち Claude が読む必要のある部分だけを返したりできます。
Code Mode は実験的な機能で、既定では無効です。ドキュメントに書かれている主な前提条件は次のとおりです。
- Claude Code v2.1.323 以降のネイティブビルド
- ターミナルの対話型
claudeセッション(-p、Agent SDK、VS Code のパネル、Desktop アプリ、クラウドセッションでは使えない) - Claude のサブスクリプションまたは Console アカウント(ゲートウェイ、Amazon Bedrock、Claude Platform on AWS、Google Cloud の Agent Platform、Microsoft Foundry 経由では使えない)
- モデルは Claude Opus 5.5、Sonnet 5.5、Haiku 5.5、Fable 5.2 のいずれか
- 組織が HIPAA 構成や zero data retention を使っていない
- Windows では Git for Windows がインストールされている
- API キーヘルパー、agent teams、OpenTelemetry のエクスポート、
DISABLE_TELEMETRYなどを使っていると起動しない
有効にする
CLAUDE_CODE_EXPERIMENTAL_CODE_MODE 環境変数を 1 にして起動します。常に有効にする場合は ~/.claude/settings.json の env に設定します(プロジェクトの設定ファイルに書いた場合は無視されます)。
筆者の環境はユーザー設定(~/.claude/settings.json の env)で CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 を設定しているため、そのまま次のコマンドで起動すると、Code Mode を使えないことを示す行が表示され、通常のツールで起動しました。
% CLAUDE_CODE_EXPERIMENTAL_CODE_MODE=1 claude --model opus

そこで、agent teams の環境変数を外し、--setting-sources project,local でユーザー設定を読み込まずに起動しました。比較の条件をそろえるため、権限モードは --permission-mode default、モデルは --model opus に固定し、筆者がふだん使っている fullscreen 表示を --settings で指定しています。
画面は tmux で起動したセッションを iTerm2 のウィンドウで表示して撮影しました。筆者は別の Claude Code セッションの中から実行したため、親セッションの環境変数を引き継がないよう、tmux のセッションを作るときに、起動コマンドの先頭に env -i を付けて最小限の環境変数だけを渡しています。iTerm2 はそのセッションに tmux attach で接続して表示しているだけです。
確認ダイアログへの回答は ~/.claude.json の codeModeCaveatsAcceptedV1 に保存されます。筆者は撮影の前に一度「Yes, turn on Code Mode」を選んでいたため、~/.claude.json をバックアップしてからこのキーだけを削除し、ダイアログをもう一度表示させました。撮影に使ったコマンドは次のとおりです(バックアップ先は変えています)。~/docs-site は次の節で使うテスト用プロジェクトです。
# ~/.claude.json をバックアップし、確認ダイアログへの回答だけを削除する
% cp -p ~/.claude.json ~/.claude.json.bak
% python3 - <<'EOF'
import json, os
p = os.path.expanduser("~/.claude.json")
d = json.load(open(p))
assert d.pop("codeModeCaveatsAcceptedV1", None) is True
tmp = p + ".tmp-cm"
json.dump(d, open(tmp, "w"), indent=2, ensure_ascii=False)
os.chmod(tmp, os.stat(p).st_mode & 0o777)
os.replace(tmp, p)
EOF
# tmux のサーバーを起動し、ステータスバーを消して、ウィンドウのタイトルを claude にする
% tmux -L cmtest new-session -d -s keep "cat"
% tmux -L cmtest set -g status off
% tmux -L cmtest set -g set-titles on
% tmux -L cmtest set -g set-titles-string "claude"
# env -i で最小限の環境変数だけを渡して、Code Mode のセッションを起動する
% CLEAN="env -i HOME=$HOME USER=$USER LOGNAME=$USER PATH=$PATH SHELL=/bin/zsh TERM=xterm-256color LANG=ja_JP.UTF-8 TMPDIR=$TMPDIR"
% tmux -L cmtest new-session -d -s cm2 -x 120 -y 40 -c "$HOME/docs-site" "$CLEAN CLAUDE_CODE_EXPERIMENTAL_CODE_MODE=1 claude --model opus --permission-mode default --setting-sources project,local --settings '{\"tui\":\"fullscreen\"}'"
# 120 列 × 40 行の iTerm2 のウィンドウで実行する
% tmux -L cmtest attach -t cm2
env -i で渡す環境変数に CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS を含めていないため、親の Claude Code セッションから引き継いだ agent teams の環境変数は渡されません。~/.claude/settings.json の env にある設定は、--setting-sources project,local で読み込まないようにしています。
初回は次の確認ダイアログが表示されます。既定の選択は「No, start without it」なので、「Yes, turn on Code Mode」を選びます。

Claude Code が再起動し、セッションの上部に Code Mode が有効になったことを示す行が表示されました。Code Mode で始めたセッションは、Code Mode を有効にしたときだけ再開できます。このとき、~/.claude.json の codeModeCaveatsAcceptedV1 は true に戻っていました。

同じタスクを Code Mode なし・ありで比べる
Markdown ファイル 5 件のテスト用プロジェクトを用意しました。ローカルファイルへのリンクのうち 5 件は、存在しないファイルを指しています。ファイルの構成と内容は次のとおりです。
docs-site/
├── README.md
├── docs/
│ ├── setup.md
│ └── usage.md
└── examples/
├── advanced.md
└── basic.md
README.md
# Sample Docs
- [セットアップ](./docs/setup.md)
- [使い方](./docs/usage.md)
- [コントリビュートの方法](./CONTRIBUTING.md)
- [公式サイト](https://example.com)
docs/setup.md
# セットアップ
[README に戻る](../README.md)
詳しいインストール手順は [install.md](./install.md) を参照してください。

docs/usage.md
# 使い方
インストールは [セットアップ](./setup.md#install) を参照してください。
サンプルは [基本の例](../examples/basic.md) にあります。
examples/basic.md
# 基本の例
API の詳細は [API リファレンス](../docs/api.md) を参照してください。
次は [応用の例](./advanced.md) です。
examples/advanced.md
# 応用の例
[README](../README.md) / [よくある質問](../docs/faq.md)
存在しないファイルを指しているリンクは、README.md の ./CONTRIBUTING.md、docs/setup.md の ./install.md と ../images/arch.png、examples/basic.md の ../docs/api.md、examples/advanced.md の ../docs/faq.md の 5 件です。
Code Mode なし(~/docs-site-normal)とあり(~/docs-site)のフォルダに同じ内容を置き、それぞれのセッションに同じプロンプトを送りました。
このプロジェクトの Markdown ファイルをすべて読み、存在しないローカルファイルを指しているリンクを一覧にしてください。
Code Mode なしの画面です(回答の表と補足は省略しています)。
❯ このプロジェクトの Markdown ファイルをすべて読み、存在しないローカルファイルを指しているリンクを一覧にしてください。
Searched for 1 pattern, read 5 files
⏺ Markdown ファイル 5 つ(README.md、docs/setup.md、docs/usage.md、examples/basic.md、examples/advanced.md)をすべて読み
ました。存在しないローカルファイルを指しているリンクは次の 5 件です。
✻ Worked for 13s · done 9:36
Code Mode ありの画面です。会話にはコードではなく、コードの中から実行されたツール呼び出しの要約(Searched for 1 pattern, read 5 files, ran 12 shell commands)が表示されています。

どちらのセッションも、存在しないファイルへのリンク 5 件を挙げました。
Claude が書いた JavaScript
Claude が書いたコードは、Ctrl+O で開くトランスクリプトビューアで読めます。コードは「Code(…) · 29 lines (click to show)」の行に折りたたまれていて、その下にコードの中から実行されたツール呼び出しが並びます。

Code 行をクリックすると、コードが展開されます。

コードの全文はセッションのトランスクリプト(~/.claude/projects/ 配下の JSONL)から、コードを実行するツール REPL の入力として取り出しました。
% jq -r 'select(.type=="assistant") | .message.content[]? | select(.type=="tool_use" and .name=="REPL") | .input.code' cm_transcript.jsonl
const files = (await $`find . -type f \( -name '*.md' -o -name '*.markdown' -o -name '*.mdx' \) -not -path '*/node_modules/*' -not -path '*/.git/*'`.text()).trim().split('\n').filter(Boolean);
const results = [];
for (const f of files) {
const lines = await file(f).lines();
const dir = f.replace(/\/[^/]*$/, '');
let inCode = false;
const refDefs = [];
lines.forEach((line, i) => {
if (/^\s*(```|~~~)/.test(line)) { inCode = !inCode; return; }
if (inCode) return;
const l = line.replace(/`[^`]*`/g, '');
const targets = [];
for (const m of l.matchAll(/!?\[[^\]]*\]\(\s*<?([^)\s>]+)>?(?:\s+["'][^)]*["'])?\s*\)/g)) targets.push(m[1]);
const rd = l.match(/^\s*\[[^\]]+\]:\s*<?(\S+?)>?(\s|$)/); if (rd) targets.push(rd[1]);
for (const m of l.matchAll(/(?:src|href)=["']([^"']+)["']/g)) targets.push(m[1]);
for (const t of targets) results.push({f, line: i+1, t, dir});
});
}
const broken = [];
for (const r of results) {
let t = r.t;
if (/^[a-z][a-z0-9+.-]*:/i.test(t) || t.startsWith('#') || t.startsWith('//')) continue;
let p = decodeURIComponent(t.split('#')[0].split('?')[0]);
if (!p) continue;
const full = p.startsWith('/') ? '.' + p : r.dir + '/' + p;
const ok = (await $`test -e ${full}`.nothrow()).exitCode === 0;
if (!ok) broken.push(`${r.f}:${r.line} ${r.t}`);
}
({count: files.length, files, linkCount: results.length, broken})
このコードは find で Markdown ファイルを列挙し、各ファイルを読んでリンクを抽出しています。そのうえで、外部 URL とページ内リンクを除いた 12 件のリンクについて for ループで test -e を 1 件ずつ実行し、ファイル数・ファイル一覧・リンクの件数・存在しないリンクを結果として返しています。Claude がこのコードの実行結果として受け取ったのは次の内容です。
<repl-output>
<tools-used-before-summary>Bash, Read</tools-used-before-summary>
<result>{
count: 5,
files: [
'./README.md',
'./docs/setup.md',
'./docs/usage.md',
'./examples/basic.md',
'./examples/advanced.md'
],
linkCount: 13,
broken: [
'./README.md:5 ./CONTRIBUTING.md',
'./docs/setup.md:5 ./install.md',
'./docs/setup.md:7 ../images/arch.png',
'./examples/basic.md:3 ../docs/api.md',
'./examples/advanced.md:3 ../docs/faq.md'
]
}</result>
</repl-output>
数値で比べる
両方のセッションのトランスクリプトを集計しました。metrics.py は筆者が作ったスクリプトで、モデルへのリクエストを message.id 単位で数え、コードの中から実行されたツール呼び出しを別に数えます。トランスクリプトでは、コードの中から実行された呼び出しは model が <synthetic> のメッセージとして記録されていました。所要時間(elapsed_sec)は、プロンプトの記録時刻から最後のアシスタントメッセージの記録時刻までです。
metrics.py
#!/usr/bin/env python3
"""セッションのトランスクリプト(JSONL)から、モデルへのリクエスト数・ツール呼び出し・トークン・所要時間を集計する"""
import json
import sys
from collections import Counter, OrderedDict
from datetime import datetime
path = sys.argv[1]
rows = [json.loads(l) for l in open(path)]
msgs = OrderedDict() # message.id -> {"types": [...], "usage": {...}}
tool_uses = []
inner = [] # コード内から実行されたツール呼び出し(<synthetic>)
for r in rows:
if r.get("type") != "assistant":
continue
m = r["message"]
if m.get("model") == "<synthetic>" or r.get("isVirtual"):
for c in m.get("content", []):
if c["type"] == "tool_use":
inner.append(c)
continue
e = msgs.setdefault(m["id"], {"types": [], "usage": None})
for c in m.get("content", []):
e["types"].append(c["type"])
if c["type"] == "tool_use":
tool_uses.append(c)
e["usage"] = m.get("usage") # 同じ id の最後の usage を使う
def ts(r):
return datetime.fromisoformat(r["timestamp"].replace("Z", "+00:00"))
prompt_rows = [r for r in rows if r.get("type") == "user" and isinstance(r["message"].get("content"), str)]
asst_rows = [r for r in rows if r.get("type") == "assistant"]
start, end = ts(prompt_rows[0]), ts(asst_rows[-1])
tot = Counter()
for e in msgs.values():
u = e["usage"] or {}
for k in ("input_tokens", "cache_creation_input_tokens", "cache_read_input_tokens", "output_tokens"):
tot[k] += u.get(k) or 0
result = {
"model_requests": len(msgs),
"tool_calls_requested_by_model": dict(Counter(t["name"] for t in tool_uses)),
"tool_calls_inside_code": dict(Counter(t["name"] for t in inner)),
"tokens": dict(tot),
"elapsed_sec": round((end - start).total_seconds(), 1),
}
print(json.dumps(result, ensure_ascii=False))
nm_transcript.jsonl(Code Mode なし)と cm_transcript.jsonl(Code Mode あり)は、~/.claude/projects/ 配下にある各セッションのトランスクリプトをコピーしたファイルです。
% python3 metrics.py nm_transcript.jsonl
{"model_requests": 3, "tool_calls_requested_by_model": {"Bash": 1, "Read": 5}, "tool_calls_inside_code": {}, "tokens": {"input_tokens": 6, "cache_creation_input_tokens": 20479, "cache_read_input_tokens": 127939, "output_tokens": 1145}, "elapsed_sec": 13.5}
% python3 metrics.py cm_transcript.jsonl
{"model_requests": 2, "tool_calls_requested_by_model": {"REPL": 1}, "tool_calls_inside_code": {"Bash": 13, "Read": 5}, "tokens": {"input_tokens": 4, "cache_creation_input_tokens": 12735, "cache_read_input_tokens": 38641, "output_tokens": 1373}, "elapsed_sec": 15.3}
Claude がツール結果として受け取った文字数も数えました(コードの中から実行された呼び出しの記録は除いています)。
% jq -s '[ .[] | select(.type=="user" and (.isVirtual|not)) | .message.content[]? | select(type=="object" and .type=="tool_result") | .content | if type=="array" then map(.text // "") | join("\n") else . end | length ] | add' nm_transcript.jsonl
651
% jq -s '[ .[] | select(.type=="user" and (.isVirtual|not)) | .message.content[]? | select(type=="object" and .type=="tool_result") | .content | if type=="array" then map(.text // "") | join("\n") else . end | length ] | add' cm_transcript.jsonl
513
| 項目 | Code Mode なし | Code Mode あり |
|---|---|---|
| モデルへのリクエスト | 3 回 | 2 回 |
| モデルが要求したツール呼び出し | Bash 1・Read 5 | REPL 1 |
| コードの中から実行されたツール呼び出し | なし | Bash 13(find 1・test -e 12)・Read 5 |
| 出力トークン | 1,145 | 1,373 |
| 入力トークン(キャッシュ作成) | 20,479 | 12,735 |
| 入力トークン(キャッシュ読み取り) | 127,939 | 38,641 |
| Claude が受け取ったツール結果 | 651 文字 | 513 文字 |
| プロンプトの記録から最後の応答まで | 13.5 秒 | 15.3 秒 |
Code Mode ありでは、モデルへのリクエストが 3 回から 2 回になり、入力トークン(キャッシュ作成・キャッシュ読み取り)と、Claude が受け取ったツール結果の文字数も少なくなりました。一方で、出力トークンと所要時間は Code Mode ありの方が多くなっています。なお、撮影の前に、表示をクラシック表示にし、テスト用プロジェクトを別のフォルダに置いて一度実行しました。このときもモデルへのリクエストは 3 回から 2 回でしたが、ツール結果の文字数(Code Mode なし 652 文字、あり 1,020 文字)と所要時間(なし 17.1 秒、あり 13.9 秒)の大小は今回と逆でした。どちらも 1 回ずつ実行した結果なので、参考値です。
権限チェックと無効にする方法
ドキュメントによると、コードの中から実行される各ツール呼び出しにも、deny・ask の権限ルールと PreToolUse フックが適用されます。auto mode では、安全チェックはツール呼び出しごとではなく実行前にコード単位で行われます(確認ダイアログにも同じ説明があります)。コードそのものを確認したい場合は、REPL に一致する PreToolUse フックで tool_input.code を読めます。今回は manual mode(--permission-mode default)で実行し、test -e などの呼び出しで権限プロンプトは表示されませんでした。
Code Mode をやめるには、シェルと ~/.claude/settings.json の両方から CLAUDE_CODE_EXPERIMENTAL_CODE_MODE を削除するか、どちらかで 0 にして、新しいセッションを始めます。一方が 0 なら、もう一方が 1 でも Code Mode は無効になります。組織で使わせない場合は、管理設定の env で CLAUDE_CODE_EXPERIMENTAL_CODE_MODE を "0" にします。実行中の Code Mode のセッションは終了か再起動まで続き、その後は 0 が設定されている間、Code Mode のセッションを再開できません。管理設定から Code Mode を有効にはできません。
また、Code Mode のセッションでは Claude in Chrome とコンピューター操作を使えず、画面の下部にも次の案内が表示されていました。
No Chrome or computer use: exit and run CLAUDE_CODE_EXPERIMENTAL_CODE_MODE=0 claude for a new session without Code …
ファイルの列挙・読み取り・存在確認を組み合わせる今回のタスクでは、Claude が 1 つのコードに処理をまとめ、モデルへのリクエストが 3 回から 2 回に減りました。小さなプロジェクトで 1 回ずつ比べた結果なので差は参考値ですが、多数のファイルを横断して確認するユースケースで試す価値があると思います。
最後に
v2.1.325 では、サンドボックス内の Bash、プロジェクトのものでも信頼済みでもないリポジトリでの読み取り専用の git、allowed-tools やフックを宣言するスキルの /loop での実行など、これまで確認なしで動いていた操作に確認が入る変更が目立つと感じます。無人実行やスケジュールタスクを運用している方は、アップデート後に動作を確認し、必要な設定や allow ルールを明示しておくのがよいと感じます。
参考文献








