「gcloud の構成は組織ごとに分けてある。だから混ざらない」。自社と別の組織の仕事を同じ Mac で AI エージェントに任せながら、そう思っていた。
なのに、ある日、自社リポジトリの tofu init が 403 で止まった。原因は、別の組織の作業で打った gcloud auth application-default login の 1 回。しかもこの穴、自分で書いた設計メモの「既知の限界」に「ADC は対象外」と載っていた。予告どおりに落ちた。
gcloud の構成は名札で、何枚でも付け替えられる。ADC は玄関の合鍵で、名札を何枚作っても 1 本しかない。誰かが作り直せば、全員の鍵が替わる。
認証は3種類あり、構成で分かれるのは gcloud 自身の分だけ
| 認証 | 作り方 | 置き場所 | 使う側 |
|---|---|---|---|
| gcloud のユーザー認証 | gcloud auth login | config dir の中。構成ごとにアカウントを選べる | gcloud 自身 |
| ADC(Application Default Credentials) | gcloud auth application-default login | config dir 直下の application_default_credentials.json 1 つ | OpenTofu / Terraform、各言語のクライアントライブラリ |
| サービスアカウント | 鍵ファイル、実行環境への付与 | GOOGLE_APPLICATION_CREDENTIALS やメタデータサーバー | CI、Cloud Run など |
公式ドキュメントでは、ADC は「環境変数のファイル → application-default login のファイル → アタッチされたサービスアカウント」の順に探す。gcloud の認証とは別物で、ログインのたびに上書きされ、場所は config dir だけで決まる(gcloud 583.0.0 のソースで確認)。名札は組織ごとなのに、合鍵は 1 本のままだった。
では、合鍵ごと分ければ済むのだろうか? 構成の説明には、CLOUDSDK_CONFIG で config dir ごと移せるとある。dir を分ければ ADC も dir に閉じる。ただし、この変数を読むかどうかはライブラリ次第だった。
| ADC を探す側 | CLOUDSDK_CONFIG | GOOGLE_APPLICATION_CREDENTIALS |
|---|---|---|
gcloud(auth application-default 系のコマンド) | 読む | 読む(こちらが優先) |
| Python / Java のライブラリ | 読む | 読む |
| Go(OpenTofu / Terraform)/ Node.js のライブラリ | 読まない。~/.config/gcloud/ 固定 | 読む |
ライブラリは各言語の公式ソースで、gcloud と OpenTofu 1.13.2 は偽の HOME で確かめた。CLOUDSDK_CONFIG を顧客 A の dir に向け、既定の場所には別の ADC を置く。確認に使う gcloud auth application-default print-access-token は顧客 A の鍵を使う。なのに実際は、tofu init が既定の場所の鍵で玄関を開けに行く。確認係と実行係が、違う鍵束を持っている。
エージェントに任せると、どこで混ざるか
人が 1 つの端末で順に作業するなら、気をつければ済む。エージェントに任せると、気をつける人がいない。構成の切り替えも ADC のログインも全セッション共通の状態を書き換えるので、並列のセッションやサブエージェント同士が互いを壊し合う。ADC の上書きで起きた 403 はログインし直せば直るが、直すたびにもう片方が壊れる(いたちごっこの完成)。
なのでエージェントには、直す前に次を確かめさせる。
| 場面 | エージェントに指示・確認させること |
|---|---|
| 403 や認証エラーで止まった | ログインのやり直しを提案する前に、書き込む先の config dir と、その dir を使う組織を言わせる |
| tofu や SDK のスクリプトを動かす前 | そのツールが読む ADC のファイルパスを言わせる。gcloud の出力で代用させない |
| 認証を確かめる | 確認は gcloud auth application-default print-access-token に、本番と同じ GOOGLE_APPLICATION_CREDENTIALS を付けて打たせる。ADC のファイルを読むのはこの系統のコマンドだけで、gcloud auth list や gcloud config list は gcloud 自身の認証を見るので確認にならない |
| ADC を分けたのに tofu の結果が変わらない | GOOGLE_CREDENTIALS などが残っていないか確かめさせる。GCS バックエンドはこれがあると ADC を使わない |
| ログイン | ブラウザが要るので人が行う。エージェントには CLOUDSDK_CONFIG 付きのコマンドを組み立てさせる |
分け方:組織ごとに config dir を作り、ADC はファイルで渡す
- 前提の確認: Google Cloud を叩くもの(tofu、SDK を使うスクリプト)と、その言語をエージェントに洗い出させる。Go か Node.js があれば
CLOUDSDK_CONFIGだけでは足りない - ログイン: 組織ごとに dir を分けてログインする。顧客 A なら
CLOUDSDK_CONFIG=~/.config/gcloud-client-a gcloud auth loginを打つ。続けて同じ前置きでgcloud auth application-default loginを打つと、ADC はその dir の直下にできる - 自動で切り替える: 実行した場所から組織を決めるラッパーを PATH の先頭に置く(下)
ラッパーは gcloud-shim という名前で置き、同じディレクトリに gcloud tofu terraform の名前で symlink を張る。呼ばれた名前で、gcloud には dir を、それ以外には ADC のファイルを渡す。
#!/usr/bin/env bash
# gcloud / tofu / terraform の名前で呼ばれ、実行した場所から組織を決めて認証の置き場所を渡す
set -euo pipefail
tool=$(basename "$0")
here=$(pwd -P)
home=$(cd "$HOME" && pwd -P)
case "$here/" in
"$home/work/client-a/"*) dir="$HOME/.config/gcloud-client-a" ;;
"$home/work/client-b/"*) dir="$HOME/.config/gcloud-client-b" ;;
*) dir="" ;; # それ以外は既定の ~/.config/gcloud のまま
esac
if [ -n "$dir" ]; then
case "$tool" in
gcloud) export CLOUDSDK_CONFIG="${CLOUDSDK_CONFIG:-$dir}" ;;
*) export GOOGLE_APPLICATION_CREDENTIALS="${GOOGLE_APPLICATION_CREDENTIALS:-$dir/application_default_credentials.json}" ;;
esac
fi
# PATH から自分のディレクトリを除いて本物を探す(symlink 越しでも同じディレクトリなら除く)
self=$(cd "$(dirname "$0")" && pwd -P)
IFS=:
set -f
for d in $PATH; do
[ -n "$d" ] && [ ! "$d" -ef "$self" ] && [ -x "$d/$tool" ] && exec "$d/$tool" "$@"
done
echo "$tool: 本物が PATH に見つからない" >&2
exit 127
先に付いている環境変数は上書きしないので、サービスアカウントの鍵を明示した実行はそのまま通る。判定は実行のたびに行うので、1 行の中で cd してから tofu を呼んでも、npm scripts が sh -c 経由で呼んでも、その時の場所で決まる。HOME に symlink を含む配置(macOS の /tmp)で、bash・zsh・dash・fish から呼んで確かめた。
ラッパーの置き場所は、シェルの設定で PATH の先頭に足す。Claude Code(Pro $20/月、Max $100/月〜)なら、SessionStart フックで CLAUDE_ENV_FILE にも PATH を書く。どこから起動したセッションの Bash にも届き、公式ドキュメントにも PATH を足す例がある。筆者の環境は両方で足している。
最後に後始末をする。既定の dir の ADC が別の組織のもので上書きされていたら、本来のアカウントで作り直す。使わなくなった構成は gcloud config configurations delete で消す。有効な構成は消せないので、先に gcloud config configurations activate default で戻しておく。
切り替え方ごとの取りこぼし
構成の切り替えは、activate でも CLOUDSDK_ACTIVE_CONFIG_NAME でも ADC が分かれない(最初の節)。もう 1 つの候補の direnv は、人の端末なら十分なのに、エージェント越しだと届かない経路がある。ラッパーと並べるとこうなる。
| 切り替え方 | ADC が分かれる | tofu に届く | cd と同じ 1 行の tofu | サブエージェント・並列セッション |
|---|---|---|---|---|
| direnv + Claude Code の CwdChanged フック | 両方の変数を書けば分かれる | 書けば届く | 前の場所の値のまま(見込み) | サブエージェントでは発火しない |
| PATH 先頭のラッパー(上) | 分かれる | 届く | その時の場所で決まる | その時の場所で決まる |
direnv の行は Claude Code のフックの仕様から読んだ見込みで、実測していない。CwdChanged はメインの会話で場所が変わったときに発火し、CLAUDE_ENV_FILE は各コマンドの前に読まれる。ラッパーにも穴はあり、ラップしていないコマンド(SDK を直接使う Python や Node.js のスクリプト)は既定の場所を探しに行く。ラッパーの case に書いていない場所(/tmp への clone など)でも、黙って既定の ADC で動く。~/work を symlink にしているなら、case の左辺は実体のパスで書く。ラッパーは玄関で鍵を配る係なので、裏口から入る人には渡せない。
まとめ
名札(構成)を分けても合鍵(ADC)は 1 本で、CLOUDSDK_CONFIG で分けても OpenTofu は既定の場所を探しに行く。組織ごとに config dir を作って tofu にはファイルで渡し、エージェントには「どのファイルを読むか」を毎回言わせる。
エージェントに呼び戻される詰まり全般はsandbox の詰まりを記録から分ける記事にまとめた。エージェントを使った開発の環境づくりを外から整えたい場合は、AI開発支援で相談を受け付けている。



