個人 Gmail を Google Workspace CLI から検索できるようにした — OAuth 同意画面が組織内部だと通らない

開発claude-code-tools

探したいメールが1通あった。CLI から検索させたら、0件で返ってきた。

認証は両方入っているつもりだった

手元の gcloud には、仕事用の独自ドメインアカウントと個人の Gmail アカウントの両方が入っている。だから gws(Google Workspace CLI)からも両方のメールボックスが見えるはずだと思っていた。

見えていたのは仕事用だけだった。gcloud のログイン状態と、gws が握っている認可はまったく別物で、後者は自分で作った OAuth クライアント経由の認可情報だけを持っている。

キーワードを変えて直近1年ぶんを洗わせても、仕事用のメールボックスには目当てのものが入っていなかった。個人の Gmail 側にある、と確定した。ところがその個人アカウントで gws の認証を通そうとすると、アカウントを選んだ先で拒否される。

弾いていたのは同意画面の公開範囲だった

ブラウザに出ている画面を貼って見せたら、原因が org_internal に絞られた。組織のプロジェクトに紐づく OAuth クライアントは、その組織のディレクトリに載っているアカウントしか認可できない。個人の Gmail アカウントは、何度アカウント選択をやり直しても通らない。

だったら個人アカウントを組織側に加えればいいだろう、と思った。Cloud Console から追加できるはずだと頼んだ。

それはできない、と返ってきた。Workspace のディレクトリに載せられるのは独自ドメインのアドレスだけで、gmail.com のアドレスはメンバーとして作れない。グループに外部ユーザーとして招くことはできるが、org_internal の判定はディレクトリのメンバーシップで行われるので、招いたところで解除されない。アカウント側を組織に寄せる方向では、この壁は動かない。

その手前で、設定ディレクトリだけ分けて既存のクライアント設定をコピーさせ、認証を試してもいた。アカウント選択画面に出るアプリ名が組織側のクライアント名のままだったので、そこでも当然弾かれた。紛らわしいのでコピーは消させた。仕事用の認証情報は別ディレクトリなので無傷。

個人アカウント名義でプロジェクトを新しく作った

組織に属さないプロジェクトを個人アカウント名義で新しく作らせた。組織に属していなければ、同意画面の公開範囲は「外部」しか選べない。選べないことがそのまま目的になる。

ここで注意が1つ付いた。「外部」かつステータスが「テスト」のままだと、リフレッシュトークンが7日で失効する。放っておくと週に1回、ブラウザ認証をやり直すはめになる。公開に切り替えて済ませた。

必要な API はプロジェクト作成の流れで有効化させた。

プロジェクト一覧を開いたら、過去に書き捨てたものが何個も並んでいた。整理するついでに、どれが生きているかを調べさせた。

アクセスログを見れば使用実績が分かると踏んだが、全プロジェクトで空だった。デフォルトの Cloud Logging は管理操作しか記録しないので、ログが無いことは未使用の証拠にならない。課金の有無・残っている API キー・サービスアカウント・ローカルのリポジトリからの参照まで当たらせて、9個削除して4個残した。用途を特定できなかった1個は、消さずに置いた。30日以内なら gcloud projects undelete で戻せる。

ブラウザをどう動かすかで一番手間取った

Console のクリック作業は自分で押すより任せたかった。ここで経路を3つ潰した。

  • ブラウザ拡張から操作する: 接続済みのブラウザが0台。接続要求を投げる先すらない
  • agent-browser にログイン済みプロファイルを読ませる: Chrome が起動中でプロファイルがロックされていて、別コピーとして立ち上がる。Google のサインイン画面が出て終わり
  • Chrome DevTools MCP を使う: サーバーの登録まではさせたが、MCP の追加は Claude Code の再起動を経ないとツールとして見えない。再起動すればこの会話が途切れる

MCP を登録したあと、ツール検索をかけても該当は0件で、拡張側のツールだけが並んで返ってきた。再起動を挟めばログイン済みの Chrome にそのまま接続できる経路はある(この Chrome のバージョンなら chrome://inspect 経由で繋がる)。それでも、進行中の作業を切る代償のほうが大きかった。

結局、開いていた Console の画面を自分で進めた。押す場所は3つだけだった。

代わりに困ったのが、アカウントの取り違えだった。普段ログインしているのは仕事用のほうなので、リンクをそのまま開くと仕事用のセッションで開いてしまう。Console 側は URL に authuser を、gws が出す認証 URL には login_hint を付けさせて、個人アカウントに固定した。

クライアントの認証情報は、値をターミナルに貼るのではなく、ダウンロードした JSON を所定の場所に置く方式を選んだ。gws 側が用意している2番目の経路で、Client ID と Secret が会話ログに載らない。

認証が通った。取得したトークンは暗号化して OS のキーリングに入る仕組みなので、設定ディレクトリを分けておけば、仕事用の認証はこの作業のあいだ一度も触られない。

同じ日の朝、仕事用のほうも作り直していた

そもそも今日は、gws の設定ごと吹き飛んだところから始まっている(消失そのものの話は別記事に書いた)。仕事用アカウントの復旧で分かったことも、この作業と地続きだった。

  • gcloud の認証は AppData 側に入っているので無傷だった。アカウントを仕事用に切り替えたら、パスワードの再認証だけで通った
  • gws auth setup のプロジェクト選択では、新規作成を選ばず既存プロジェクトの ID を手打ちした。既存なら API 有効化と同意画面の設定が前のまま残っていて、やり直しが最小で済む
  • 実際、必要な API は22件すべて有効のままだった(有効化0件・スキップ22件と表示された)
  • OAuth クライアント本体も残っていた。消えたのは手元の client_secret.json だけ。ただし Google の仕様が変わって、既存シークレットの表示とダウンロードはもうできない。手元のファイルを失った時点で、そのシークレットは永久に取り出せない。シークレットを追加して JSON を落とし直した

client_secret.json は消えたら再発行しかない。バックアップ対象として扱わないと、また同じ復旧をやることになる。

日本語のクエリが Git Bash で溶けた

検索させた最初の1回、ヒット件数が明らかに多すぎた。日本語のキーワードが Git Bash を通る間に壊れて、クエリとして成立せず全件が返っていた。Unicode エスケープで渡し直させたら、10件に落ちた。

朝も同じ地雷を踏んでいる。ターミナルに日本語をそのまま打ったら、化けたバイト列が command not found で返ってきた。同じ日に2回出てくるなら、Windows の Git Bash 経由で日本語を渡す経路は最初からエスケープ前提でいい。

使い分けを決めてスキルに書かせた

認証が通ったので、探していたメールを検索させて、返信の下書きを Gmail に保存させた。元のスレッドへの返信として紐付いている。送信はしていない。

ついでにカレンダーの API も有効化させた。日程を詰める材料が両方のカレンダーから引けるようになって、片方だけ見て予定を埋める事故がなくなった。

最後に、アカウントの使い分けを決めた。スプレッドシートや Drive のドキュメント類は仕事用アカウントに集約する。個人アカウント側にドキュメントを残す予定はない。ただしメールとカレンダーは両方で動いているので、指定がなければ両方を検索してほしい。この切り分けを gogcli スキルに追記させた。スキル一覧の説明文にも載るので、次のセッションでは指示なしでこの前提で動く。

学び

  • gcloud にアカウントが入っていることと、gws がそのアカウントを認可できることは別の話。CLI が握っているのは自分で作った OAuth クライアント経由の認可情報だけ
  • 同意画面が org_internal のクライアントでは、組織外の個人アカウントは絶対に通らない。アカウントを組織に入れる方向では解決しないので、プロジェクトとクライアントを分ける
  • 個人アカウント用は、プロジェクト・OAuth クライアント・設定ディレクトリの3点セットで分離するのが素直
  • 「外部」の同意画面はテストのままだとリフレッシュトークンが7日で切れる。公開にしておく
  • OAuth クライアントのシークレットは、作成時にダウンロードした JSON が唯一の控え。あとから表示もダウンロードもできない
  • 日本語のクエリは Unicode エスケープで渡す。全件ヒットしたら、まずクエリが壊れていないかを疑う
  • MCP サーバーを足しても、そのセッションからは見えない。ブラウザ操作を任せたいなら、作業を始める前に接続を作っておく
  • Cloud Logging が空でも未使用の証拠にはならない。プロジェクトの棚卸しは課金・鍵・ローカルの参照から当てる