Claude Media
Claude Codeが.envをBash環境へ読み込んでいた問題 — 原因とv2.0.64の修正

Claude Codeが.envをBash環境へ読み込んでいた問題 — 原因とv2.0.64の修正

Claude CodeがプロジェクトのBash実行環境へ.envを自動読み込みしていた既知の問題の原因、v2.0.64での修正、今も有効な3層の対策をまとめます。

Claude Codeには、プロジェクトの.envファイルをBashツールの実行環境へ気づかないまま読み込んでしまう不具合がありました。LaravelやRailsのテストスイートが開発用データベースを誤操作し、データが失われた実害も報告されています。.envに秘密情報を置いている場合は、意図しない相手にその値が渡る経路にもなります。原因は2つに分かれ、片方は今も仕様として残り、もう片方はv2.0.64で修正されました。

Claude Codeが.envを読み込むとテストが壊れる

GitHub issue #401は2025年3月8日に報告されました。報告者はLaravelのテストスイートを実行するたびに、開発用データベースの内容が消える現象に悩まされていました。原因を調べると、Claude Codeが起動時にプロジェクトの.envファイルをBashツールのプロセス環境変数として読み込んでいたと判明します。phpunit.xmlで指定したテスト用データベースの設定は、.env由来のプロセス環境変数に上書きされていました。

同様の報告は半年近く途切れませんでした。2025年7月には複数の開発者がv1.0.58でも.env.developmentが混入すると確認し、2025年10月17日には利用者のMichMich氏が「phpunitのテストを実行するたびにデータベースが消される」と改めて報告しています。

影響はWebフレームワークだけに留まりませんでした。2025年10月7日、bpedman氏はAmazon Bedrock経由でClaude Codeを使う環境で、.envに書いていたAWS_PROFILEがsettings.jsonで指定した値を上書きし、モデルにアクセスできなくなったと報告しています。チームで共有しているBedrock用プロファイルが、開発者個人の.envの値に静かに差し替わっていた格好です。データ破壊と認証失敗という、性質の違う2種類の実害が同じ根から出ていたことになります。

原因は2つある — 仕様のシェル継承と意図しない自動読み込み

Anthropicのbcherny氏は報告直後、この挙動の一部を仕様として説明しました。Claude Codeは起動時に~/.zshrc~/.bashrcをソースし、AWSやGCPの認証情報を含む環境変数をBashツールに引き継ぎます。ユーザーの代わりにクラウドサービスを操作させるための設計で、この部分は今も変わっていません(シェルごとの読み込みタイミングは別記事にまとめています)。

問題はもう一つ別にありました。2025年8月8日、rsanheim氏がネイティブインストーラー内部で使われるBunランタイムの挙動を突き止めます。BunはNODE_ENVが未設定か空文字のときdevelopmentとみなし、.envのあとに.env.developmentを読み込みます。NODE_ENV=productionのときは.envのあとに.env.productionを読み込みます。厄介なのはここからで、Bunが読み込んだ値はプロセスの環境変数になるため、アプリ側のdotenvライブラリが.env.testを読もうとしても、既存のプロセス環境変数を上書きしない実装がほとんどです。結果として.env.testに書いた値ではなく、先に読み込まれた.env.developmentの値が実行時に使われていました。

Ruby on Railsのdotenv-railsを使うチームも同じ経路の影響を受けました。LandonSchropp氏によれば、.env.developmentRAILS_ENV=developmentを書く構成では、Bunがこの値を読み込んだ時点でBundlerがtestグループのgemを読み込まなくなり、Claude経由のテスト実行そのものが失敗していました。この自動読み込みは常に起きるとは限らず、Dambre氏は「数日前から急に起こるようになった」と報告しており、発生条件の分かりにくさが原因調査を長引かせた一因になっています。

この読み込みはClaude Codeのプロセス起動と同時に走るため、hooksやCLAUDE.mdの指示より先に完了していました。joenyambura氏は2025年8月7日、最速のSessionStart hookでもこの読み込みには間に合わないと報告しています。hooksという後付けの制御点そのものが、起動シーケンス上そもそも間に合わない位置にあったわけです。

v2.0.64で.envの自動読み込みが修正された

時期出来事
2025-03-08出来事issue #401が報告される。bcherny氏がシェル継承は仕様と説明
2025-07-23出来事v1.0.58でも.env.developmentの混入が複数人から再確認される
2025-08-08出来事rsanheim氏がBunのNODE_ENV依存読み込みを原因として特定
2025-10-17出来事v2.0.1/v2.0.8でも継続。MichMich氏がデータベース破壊を再報告
2025-12-10出来事v2.0.64で「Fixed auto-loading .env when using native installer」と修正

公式changelogは2025年12月10日付のv2.0.64で「Fixed auto-loading .env when using native installer」と記載しています。報告から9か月あまりを経ての修正でした。issue #401自体はGitHub上のステータスとしては早い段階で「closed」に変わっていましたが、Bun由来の自動読み込みについての実害報告はその後も続きました。issueのクローズと実際の修正が別のタイミングだった点は、リンクだけを見て「解決済み」と早合点しないための注意点です。公式changelogのv2.1.278までの記録に、.envの自動読み込みが再発したという記載はありません。

修正前の時期には、インストール方法を変える回避策も報告されていました。2025年10月6日、yuheitomi氏はネイティブインストーラーからレガシーなnpmインストーラーに切り替えると問題が起きなくなったと報告しています。当時のnpm版はBunを経由しない配布形態だったため、結果的にこの不具合を避けられていました。

ただしこの回避策は今では成立しません。npm経由のインストールも、v2.1.198以降はネイティブインストーラーと同じバイナリを配布する形に統一されています。2025年当時の回避策だった「npmの旧インストーラーに戻す」という手順は、v2.1.198以降のインストール方式には当てはまりません。

今も使える3つの対策層

根本原因は塞がれましたが、.envの値がBashの実行環境に混ざる経路はまだ残っています。プロジェクトのMakefileやシェルスクリプトがsource .envを呼ぶ構成では、同じ現象が別の形で起こり得ます。守りたい値の性質に応じて、3つの対策を使い分けます。

対策防ぐ範囲必要バージョン効かないもの
permissions.denyRead(./.env)防ぐ範囲Read/Editツールとcatheadtailsedなど認識済みのBashコマンドがファイル内容を表示すること必要バージョンバージョン不問効かないもの既にプロセス環境へ読み込まれた値をenvで確認する操作、grep -r、任意のサブプロセス
sandbox.credentials.envVars(deny/mask)防ぐ範囲指定した変数名をサンドボックス化されたBashコマンドの環境から除去または置換必要バージョンdenyv2.1.187以降・maskv2.1.199以降効かないもの列挙していない変数名、サンドボックスを有効にしていないセッション
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB防ぐ範囲Claude Code自身が認証情報と認識する変数を、Bash・hooks・MCPの子プロセス全体から除去必要バージョン既定は無効効かないものRAILS_ENVDATABASE_URLなど認証情報でないアプリ設定値

そもそも今回のBun起因の問題は、Read拒否ルールでは止められませんでした。値はプロセス起動と同時にBashの環境変数として届いており、Read/Editツールを経由しないため対象外だったからです。OSレベルで止めるには、permissions.denyとサンドボックスのcredentials.envVarsを組み合わせます。設定例は次の通りです。

{
  "permissions": {
    "deny": ["Read(./.env)", "Read(./.env.*)"]
  },
  "sandbox": {
    "enabled": true,
    "credentials": {
      "envVars": [
        { "name": "DATABASE_URL", "mode": "deny" },
        { "name": "RAILS_ENV", "mode": "deny" }
      ]
    }
  }
}

この設定は~/.claude/settings.jsonに書けば自分専用、.claude/settings.jsonに書いてリポジトリへコミットすればチーム全員に同じ制約を配れます。credentials.envVarsはサンドボックス機能の一部なので、sandbox.enabledtrueにしていないセッションでは適用されません。恒常的に守るなら、~/.claude/settings.jsonでサンドボックスごと有効化しておく必要があります。sandbox.credentials.envVarsには組み込みの拒否リストもなく、変数名は自分で列挙しなければなりません。maskモードは許可したホストへの通信でだけ実値に差し替わる仕組みで、GH_TOKENのように送信先をapi.github.comなどに絞れるトークンに向いています。プロジェクトの.envにあるようなローカル専用の接続文字列は送信先を絞りにくいため、denyで丸ごと外すほうが実用的です(マスキングの詳細設定は別記事で扱っています)。

CLAUDE_CODE_SUBPROCESS_ENV_SCRUBはBash・hooks・MCPのstdioサーバーすべてに効く一方、対象はClaude Code自身が認証情報と認識する変数に限られます。issue #401で問題になったRAILS_ENVDATABASE_URLのような、認証情報ではないアプリ設定値までは除去しません。用途を混同すると防御に穴が空きます。遮断範囲は専用記事で詳しく扱っています。

よくあるつまずき

CLAUDE.mdに「.envを読むな」と書くだけの対策が、コミュニティで一時期広まりました。2025年9月にfatihaziz氏が公開した手順で、実際に拒否メッセージを再現できます。この手順が効くのは、Claudeがモデルとして指示文を読み、自発的にファイルへのアクセスを控えるからです。権限システムのように強制的にブロックしているわけではありません。本人も「長い会話では従わないことがある」と注記しており、プロンプト上の指示なのでBashの実行環境そのものは制御できません。恒常的に守りたい値がある場合は、前段で挙げたpermissions.denyかサンドボックスの設定に置き換えるほうが確実です。

Laravelのphpunit向けには、joenyambura氏が2025年8月に示した回避策が今も有効です。phpunit.xml<env><server>に書き換えると、Bashの環境変数より<server>の値が優先されるため、Claude経由のテスト実行でもSQLiteのインメモリDBを使えます。

<php>
    <server name="DB_CONNECTION" value="sqlite"/>
    <server name="DB_DATABASE" value=":memory:"/>
</php>

根本原因が塞がれた今も、破壊的な操作の前にプロセス環境を確認する習慣は残しておく価値があります。シェル継承自体は仕様のままなので、思わぬ変数が紛れ込む余地は消えていません。テスト実行やマイグレーションのような取り返しのつかない操作の直前は、次のように該当しそうな変数名を指定して確認できます。

env | grep -E 'DATABASE_URL|RAILS_ENV|NODE_ENV'

まとめ

Claude Codeが.envをBashの実行環境へ意図せず読み込む不具合は、ネイティブインストーラーが使うBunランタイムのNODE_ENV依存の挙動が原因で、2025年12月10日のv2.0.64で修正されました。一方でシェル起動ファイルを読み込む仕様自体は変わっておらず、プロジェクト側のスクリプトが.envをsourceする構成では同種の混入が今も起こり得ます。permissions.deny・サンドボックスのcredentials.envVarsCLAUDE_CODE_SUBPROCESS_ENV_SCRUBは防ぐ範囲がそれぞれ異なるため、守りたい値の性質に合わせて組み合わせる必要があります。古いバージョンのまま運用しているプロジェクトが残っていないか、claude --versionで一度確かめておく価値はあります。特にCIやコンテナで固定バージョンを使っている場合は見落としがちです。

この記事を共有:XはてブLinkedIn