関連 issue: #76, #74
AgyReviewMaid、registry、review route: 実装済み現在の review route は agy_review だが、現行の token file precheck は keyring-only 構成を偽陰性にし得る。したがって、以下の改訂が完了して実機 smoke を通すまでは production-ready と扱わない。
採用する方針は次のとおり。
~/.gemini の全面 pass-through / symlink は行わない。auth_required を返す。auth_required を need_input に変換し、通常端末での認証と再実行を案内する。実装状況(2026-06-24): Gate 0 PASS。本実装と Codex レビュー指摘(version drift 優先 / auth 判定は log のみ / 全 status redaction / log reader の symlink 拒否・prefix read)まで反映済み。
tests/426+ passed。残るのは真の未認証実機 smoke(安全に再現不能・既知制約)と route smoke / 利用ガイド追記。
以下は当初「実施前の判断」として書いた手順だが、Gate 0(Probe A)を実機実施した結果に合わせて実績ベースへ更新済み。当初は「最小 allowlist で keyring silent auth が成立するか」を未確認の仮説として allowlist 採否を Gate 0 の分岐に置いていたが、Probe A の結果 allowlist は不要(何もコピーしない isolated HOME で silent auth 成立)と確定したため、allowlist 関連の手順は撤回した。
production code 変更前に scripts/agy_probe_a_spike.py を単独実施した。
google_accounts.json snapshot 追加 → 結果同じ。account context 不要を確認。~/.gemini の mtime / hash 不変、token / credential 非コピー、MCP/plugins 非露出を確認。判定結果: PASS(allowlist 不要が確定)。これに基づき本実装は「credential も account context も一切コピーしない」方針で進めた。
--log-file のみを判定根拠とする auth arbiter と redaction を追加。(auth marker は log にのみ出る/stdout は混ぜない)auth_required -> need_input と通常端末向け案内を確認。precheck だけを先に外し、実行レベルの認証判定が無い中間状態を作ってはいけない。1〜5は一つの未公開変更系列として完成させ、各段階で fail-closed を維持する。
#74 は Antigravity CLI 移行全体の親 issue として Open を維持する。
以下は承認済み成果として維持し、やり直さない。
cleanup_survivor / cleanup_unverifiedPhase C のうち、auth readiness / isolated HOME の成立だけを未完へ戻す。#76 を review / Wiki 共通 runtime の先行ブロッカーとして完了させ、その後に #74 の review smoke、Wiki 統合、route / rollback 確認へ戻る。
default_keyring_checker() は OS keyring を問い合わせず、~/.gemini/antigravity-cli/antigravity-oauth-token の token.refresh_token の有無だけを確認する。
実機では次の食い違いを確認した。
agy_auth_status() は keyring_unavailable を返した。ChainedAuth: authenticated via keyring (effective: keyring) と Print mode: silent auth succeeded を記録した。したがって、token file の欠落は「認証不能」の根拠にならない。現行 checker は最大でも readiness hint であり、capability 実行を止めてはいけない。
旧版は antigravity-oauth-token を isolated HOME へコピーする設計だった。しかし agy 1.0.10 のバイナリ内にはこのファイル名の根拠がなく、実環境では OS keyring 経路で silent auth が成功している。
credential file の存在・形式は環境依存であり、credential 自体を project state へ複製する設計は採用しない。
実 ~/.gemini には config/mcp_config.json、plugins、projects、conversation、history、cache、updater などが存在する。これらを isolated HOME から見せると次の問題が生じる。
また、find_mcp_config_files() が使う Path.rglob() は symlink 先ディレクトリへ降りないことを実機再現した。ディレクトリ単位の pass-through symlink は既存 MCP guard をすり抜ける。
agy 1.0.10 のバイナリと実 log で次の marker を確認した。
成功:
Print mode: silent auth succeeded
認証試行開始:
Print mode: not authenticated, trying silent auth
認証失敗 / OAuth 開始(確定 marker。これらのみで auth_required 判定):
Print mode: silent auth failed, triggering OAuth
Authentication required. Please visit the URL to log in:
Error: authentication failed:
成功時にも出る noise marker(判定に使わない。Gate 0 / Probe A 実機確認):
Print mode: not authenticated, trying silent auth
You are not logged into Antigravity.
Failed to get OAuth token
failed to set auth token
not authenticated, trying silent auth も You are not logged into Antigravity. も silent auth 成功時に先行して出るため、これらを auth_required の根拠にしてはいけない。判定根拠は request 所有の --log-file のみ(stdout は agy の回答本文で、review 対象 diff / prompt injection により marker 文字列を含み得るため混ぜない)。
Brain / MCP run_review
-> TaskContract(intent="review")
-> AgyReviewMaid.execute()
-> Butler が git diff / context を取得
-> prompt に対象を埋め込む
-> isolated project HOME を初期化
- deny-all settings: Butler 管理の実体
- Stop hook: Butler 管理の実体
- credential / account context / MCP / plugins: 一切持ち込まない
-> run_toolless()
-> version/hash gate before
-> empty workspace
-> agy --sandbox --log-file <request-owner-only-log> ...
-> auth arbiter が request log + stdout/stderr を監視
- silent auth success: 通常処理を継続
- OAuth marker: process tree を停止
-> cleanup / version/hash gate after
-> Stop event / transcript strict binding
-> auth_required は need_input へ変換
-> ok の場合だけ review response を返す
run_review の MCP インターフェースは既存と同じにし、agy に repository access を与えない。quick / thorough とも Butler が取得した diff / context だけを prompt へ渡す。
対象:
butler/agy_auth.pybutler/runtime.pybutler/agy_runtime.pyconfig/maid_registry.yml要件:
need_input を返さない。agy_auth_status() は binary 不在など確実な失敗と、token file hint / unknown を区別する。default_keyring_checker は実体に合う名称へ変更するか、少なくとも「OS keyring を確認しない」ことを API / docstring で明示する。対象:
butler/agy_runtime.py(initialize_project_home)方針(Gate 0 / Probe A で確定):
google_accounts.json 等)もコピーしない。Probe A で「何も渡さない isolated HOME でも silent auth 成立」を実機確認したため、当初案の最小 allowlist snapshot は不要になった。antigravity-oauth-token コピーは撤去済み。auth_required → need_input)。isolated HOME に持ち込まないもの(明示禁止):
settings.json / config/hooks.json 以外の実 configmcp_config.json / config/plugins/antigravity-cli/conversations/ / history/ / cache/ / updater/対象:
butler/agy_runtime.pybutler/agy_toolless.pyagy の --log-file を利用し、request ごとの owner-only log path を指定する。共有の実 cli.log は判定根拠にしない。
arbiter は request log と stdout/stderr を監視し、次の状態を区別する。
| 状態 | 判定 |
|---|---|
| silent auth 成功 marker | 実行継続 |
| OAuth 開始 / authentication failed marker | auth_required |
| outer deadline | timeout |
| panic / fatal / unknown contract | failed |
| Stop event + transcript 契約成立 | ok |
OAuth marker 検出後も、即 return して cleanup を飛ばしてはいけない。次の順を守る。
auth_required + failure_reason を返す。未認証時に agy 自身が行う30秒の OAuth waitを待ち切る必要はない。確定 marker をストリーム中に検出した時点で停止する。
AgyReviewMaid は次を維持する。
if result.status == "auth_required":
return need_input(
task.task_id,
auth_required_message(result.failure_reason),
safe_evidence,
)
案内文には必ず次を含める。
Butler / Brain は認証 URL を裏で開いたり、認証コード入力を待ったりしない。
現在の設定:
| 項目 | 値 |
|---|---|
| review route | agy_review |
| agy review model | Gemini 3.5 Flash (High) |
| legacy rollback maid | gemini_cli_review を registry に維持 |
route は実装済みだが、#76 の実機完了条件を満たすまでは production-ready としない。修正後に quick / thorough、issue comment 投稿、route rollback を確認する。
scripts/agy_probe_a_spike.py)実装作業の最初に行う Gate 0。実機結果は次のとおりで PASS(issue #76 comment 参照)。
結果:
silent auth succeeded と review 応答を確認。→ allowlist 不要が確定。google_accounts.json を追加しても結果は同じ(不要を裏付け)。~/.gemini の mtime / hash は不変。token / credential はコピーしていない。You are not logged into Antigravity. は silent auth 成功時にも出る noise → 確定失敗 marker から除外(§検証で確定した事実を更新済み)。この結果に基づき本実装(allowlist なし・arbiter は log のみ・全 status redaction)へ進んだ。
未認証の isolated HOME で実行する(account context は元々コピーしないので、認証情報が無い状態を作る)。
PASS 条件:
auth_required と安定した failure reason を返す。need_input と通常端末向け手順になる。実機制約: keyring fallback により真の未認証状態を安全に再現できない(D-Bus/XDG を外しても
effective: keyringで認証成立)。よって本 Probe B は、実バイナリの確定 marker 文字列を使った integration test(test_run_toolless_arbiter_detects_oauth_and_redacts/_aborts_before_deadline)で代替する。真の実機 unauth smoke は実際にログアウトした環境でのみ実施可能。
FileNotFoundError 以外)は握り潰さず sensitive_log_cleanup_failed で fail-closed にし、所在 path のみ evidence に返す。auth_required に変換する。not authenticated, trying silent auth 単独では失敗にしない。AgyReviewMaid が auth_required を need_input に変換する。現行 baseline:
UV_CACHE_DIR=/tmp/uv-cache uv run pytest -q \
tests/test_agy_runtime.py \
tests/test_agy_toolless.py \
tests/test_agy_auth.py \
tests/test_agy_review_maid.py
175 passed
変更後は上記に追加テストを加え、#74 の deny / cleanup / Stop hook probeも再実行する。
run_review(..., depth="quick") が成功する。depth="thorough" が成功する。issue_id 指定時にreview commentが投稿される。need_input を返す。run_toolless が auth_required + failure_reason を返す。need_input と通常端末向け手順を返す。docs/Butler利用ガイド.md §5.4)。#76 は「認証済み成功系」と「未認証 need_input 系」の両方が実機で成立した時点でcloseする。
route 経路(run_review → execute_task → AgyReviewMaid)を実機で回したことで、ユニットテストでは検出できていなかった2件の実バグを発見し修正した。
project_key() を位置引数で呼んでいた(butler/maids/agy_review_maid.py)。project_key は keyword-only なのに project_key(str(cwd)) と呼んでおり、route 経由の review が TypeError: project_key() takes 0 positional arguments but 1 was given で即失敗していた。maid テストは project_key をすべて mock していたため未検出。→ project_key(project_root=str(cwd)) に修正。回帰防止に happy-path テストの mock を autospec=True(keyword-only シグネチャ強制)へ。comment_issue() は失敗を例外ではなく status dict で返すが、maid は例外のみ捕捉し issue_comment_posted=True を立てていた。投稿に失敗(セッション未初期化・backend error 等)しても偽成功になる。→ 戻り値 status == "ok" を確認し、失敗時は理由を review_text 末尾に残すよう修正。失敗 dict 時に posted=False になる回帰テストを追加。review / Wiki用の認証処理を別々に実装してはいけない。#76 で確定するshared runtimeを唯一の実装とする。