「アプリは起動するし、エージェントもエラーを出さありません。でも API キーが認証に通らない」。Antigravity でアプリ開発を進めていて、こういう「半分動いて半分動かない」状態に陥ったことはないでしょうか。原因のかなりの割合は、AI エージェントが .env ファイルの値を 見た目どおりに 読んでいないことにあります。
私自身、2014年から続けている個人開発のアプリ群(Dolice 名義の壁紙・癒し系アプリで AdMob による広告収益を回しています)で、Firebase・Stripe など複数の鍵を .env で管理していて、Antigravity に移行した直後の数日間、まさにこの罠を立て続けに踏みました。Stripe の STRIPE_SECRET_KEY が一文字も違わないはずなのに 401 を返す、AdMob の Application ID が「設定されているはず」なのにテスト広告すら出ありません。最終的にどれも .env のクォートと空白の扱いの差が原因でした。以下では、エージェントが .env を取り違える代表的な4パターンと、その切り分け方・恒久対策を順にご紹介します。
なぜ Antigravity のエージェントだけ .env を読み違えるのか
.env ファイルは便利な反面、正式な仕様が存在しません。dotenv 系のライブラリは Node.js、Python、Ruby、Go でそれぞれ少しずつ実装が異なり、特に「クォートが値の一部なのか区切り文字なのか」「シェルの export 前置詞を許すのか」「空白をトリムするのか」の解釈が割れます。
Antigravity の AI エージェントは、コードを書く際に内部的に .env の中身を シェル互換のルール で推測することが多いです。一方、実行時に動く dotenv パッケージ(require('dotenv').config())は、シェルとは別の独自ルールで値をパースします。この「エージェントの解釈」と「実行時の解釈」がずれることで、起動はするのに値だけがおかしいという、デバッグしにくい状況が生まれます。
まず私が現場で踏んだ4つの典型パターンを並べてから、それぞれの再現条件と対処を見ていきます。
- パターン1: ダブルクォートが値の一部として残ってしまう
- パターン2: 末尾の空白・タブが値に紛れ込む
- パターン3:
export前置詞でエージェントだけが値を見失う - パターン4:
#を含む値がコメントと誤認される
パターン1: ダブルクォートが値の一部として残る
最も頻繁に出会うのがこれです。Stripe の Secret Key を .env に書くとき、ふだん API ダッシュボードからコピーすると引用符は付いてきません。ところが、エージェントに「Stripe の鍵を .env に追加して」と頼んだとき、エージェントが気を利かせて次のように書くことがあります。
# .env
STRIPE_SECRET_KEY="sk_test_YOUR_PLACEHOLDER_VALUE_HERE"Node.js の dotenv v16 以降はこのクォートを正しく剥がします。しかし、シェルから直接 source .env した場合や、docker run --env-file .env で起動した場合、Docker の --env-file パーサーは クォートを値の一部として扱います。結果として、コンテナ内部での値は "sk_test_..." という、先頭末尾にダブルクォートが付いた文字列になります。これが Stripe に送られると 401 になります。
切り分け方
実際に値がどう読まれているかは、その場で出力するのが一番速いです。
# Node.js プロセス内での値を確認
node -e "require('dotenv').config(); console.log(JSON.stringify(process.env.STRIPE_SECRET_KEY))"
# 期待する出力: "sk_test_..." ← クォートが文字列の外側だけにある状態
# Docker --env-file の場合
docker run --rm --env-file .env alpine sh -c 'echo "[$STRIPE_SECRET_KEY]"'
# 期待する出力: [sk_test_...]
# 異常な出力: ["sk_test_..."] ← クォートが値に紛れている対処
シンプルですが、.env に書くときはクォートを付けない ルールに統一するのが一番です。値に空白を含む場合のみクォートを使い、それ以外は素のまま書きます。エージェントにも .env のフォーマットを指示する際に「クォートで囲まないこと」と一文添えると、生成される .env が安定します。
パターン2: 末尾の空白・タブが値に紛れ込む
これは私が一番デバッグに時間をかけた問題です。AdMob の Application ID(ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY の形式)を .env に貼り付けたあと、react-native-google-mobile-ads の初期化で「invalid application id」と言われ続けました。
エージェントが書いてくれた .env を VS Code の hex 表示で見たところ、末尾に \t(タブ文字)が一つだけ紛れ込んでいたのです。エージェントが整形のためにインデントを揃えようとした副作用でした。
# 一見問題なさそうに見える .env
ADMOB_APP_ID=ca-app-pub-1234567890123456~1234567890
# 実際: 値の末尾にタブ文字が1つある切り分け方
cat -A で不可視文字を全部表示します。
cat -A .env | grep ADMOB
# 期待する出力: ADMOB_APP_ID=ca-app-pub-1234567890123456~1234567890$
# 異常な出力: ADMOB_APP_ID=ca-app-pub-1234567890123456~1234567890^I$
# ↑ ^I がタブ、$ より前に余計な文字があるのが異常対処
一括で末尾の空白・タブを除去するなら次のコマンドで足ります。
# 各行の末尾の空白・タブを除去(macOS の場合)
sed -i '' -E 's/[[:space:]]+$//' .env
# Linux の場合
sed -i -E 's/[[:space:]]+$//' .env恒久対策としては、リポジトリに .editorconfig を置いて trim_trailing_whitespace = true を強制するのが効きます。エージェントが新しい行を書いたときも自動でトリムされるので、再発を防げます。
パターン3: export 前置詞でエージェントだけが値を見失う
シェルで .env を source する運用をしている人は、こう書くことがあります。
# .env
export DATABASE_URL=postgres://user:pass@localhost:5432/appシェルでは export の有無は関係なく動きますし、Node.js の dotenv v17 以降は export 前置詞を黙って無視してくれます。ところが、Antigravity のエージェントがこの .env を解析してコードを書くとき、DATABASE_URL というキーを export DATABASE_URL として認識してしまうことがありました。私のケースでは、エージェントが Prisma の設定で「環境変数 export DATABASE_URL が見つからない」というエラー処理を書こうとしているのを見て気付きました。
切り分け方
実装ファイル側で、エージェントがどの変数名で参照しているかをチェックします。
# プロジェクト全体で env 参照を grep
grep -rn "process.env\." src/ | grep -i "export"
# ヒットがあれば、エージェントが export を変数名の一部と誤認している対処
混乱を避けるためには、.env から export を外すのが一番です。シェルで読み込みたい場合は別途 .env.sh のようなファイルを用意するか、set -a; source .env; set +a の形式を使えば export を書かなくても全変数が export されます。
# .env から export を一括削除
sed -i.bak -E 's/^export[[:space:]]+//' .env
# .env.bak はバックアップとして残るので、問題なければ削除
# シェルで読み込むときは set -a を使う
set -a
source .env
set +aパターン4: # を含む値がコメントと誤認される
Stripe の Webhook シークレットや一部のパスワードに # が含まれていると、.env のパーサーがそこから後ろをコメントと判定することがあります。
# .env
WEBHOOK_SECRET=whsec_abc#def123 # ← #def123 が落ちる実装があるNode.js の dotenv v16 以降は、クォートで囲まれていない値の # 以降をコメントとして切り捨てます(公式の挙動)。エージェントもこの仕様を知っていますが、.env を生成する側ではしばしばクォートを忘れます。
対処
値に # $ \ を含む可能性がある場合は、明示的にシングルクォートで囲みます。シングルクォートにすれば中の特殊文字は一切展開されず、コメント解釈もされません。
# .env
WEBHOOK_SECRET='whsec_abc#def123'
PASSWORD='p@ss$word!'ダブルクォートだと $ が変数展開の対象になり、これはこれで別の罠を生むので、機械生成された秘密情報には シングルクォート優先 が無難です。
エージェントに同じミスを繰り返させない予防策
ここまでの4パターンは、その場で直せば済みますが、エージェントに同じ生成パターンを繰り返されると意味がありません。私は次の3つを .cursorrules や AGENTS.md に書いて運用しています。
# AGENTS.md(抜粋)
## .env ファイルを編集する際のルール
1. 値はクォートで囲まない。空白や `#` `$` を含む場合のみシングルクォート(`'value'`)を使う
2. `export` 前置詞は付けない。シェルで読む場合は `set -a; source .env; set +a` を使う
3. 行末の空白・タブは入れない
4. 値を貼り付けたら必ず `cat -A .env | tail -n 1` で末尾を確認する加えて、リポジトリには dotenv-linter を入れて pre-commit で走らせると、人間の手動編集にも保険がかかります。
# dotenv-linter を pre-commit に組み込む例(.pre-commit-config.yaml)
- repo: https://github.com/dotenv-linter/dotenv-linter
rev: v3.3.0
hooks:
- id: dotenv-linterここまで揃えると、新規プロジェクトで .env がらみのデバッグに時間を取られることは、私の経験上ほぼなくなりました。
全体を振り返って
.env は仕様がないぶん、AI エージェントと実行時ライブラリのあいだで解釈がずれやすいファイルです。今日から取れる対策は次のひとつだけで十分です。「.env の値はクォートを付けない/付けるならシングルクォート、末尾の空白は許さない、export は書かない」というルールを AGENTS.md に明文化する。これだけで、半分動いて半分動かないという気持ちの悪いデバッグから抜け出せます。
同じパターンに悩んでいる方の参考になれば嬉しいです。お読みいただきありがとうございました。