Claude CodeのapiKeyHelperがVertexゲートウェイの401で再実行されない原因と回避
VertexモードでCLAUDE_CODE_SKIP_VERTEX_AUTHを使うゲートウェイ構成では、401でもapiKeyHelperが再実行されません。再現条件とTTLでの回避、修正状況をまとめます。
社内のLLMゲートウェイをVertexモードで使い、短命のトークンをapiKeyHelperから渡している構成では、トークンが切れて401が返ってもapiKeyHelperが再実行されません。同じトークンでのリクエストが、キャッシュの寿命が尽きるまで失敗し続けます。anthropics/claude-codeのissue #105408で報告された不具合で、issueはopenのままで、修正版は確認されていません。
報告があったのはClaude Code 2.1.308です。手元のバージョンはclaude --versionで確かめられます。
この記事は、自分の構成が該当するかの判定、症状の再現、CLAUDE_CODE_API_KEY_HELPER_TTL_MSによる回避、修正の追い方を扱います。
どんな構成で何が起きるのか
報告された構成は、ゲートウェイがGCPの認証情報を持ち、利用者には有効期間60分のOktaのIDトークンをapiKeyHelper経由で配る形です。Claude CodeはそのトークンをAuthorization: Bearerとして送ります。Vertex側の認証はゲートウェイが肩代わりするので、Claude Code側ではCLAUDE_CODE_SKIP_VERTEX_AUTHを立ててGoogle認証を省いています。
キャッシュ済みのトークンが切れると、ゲートウェイは401を返します。このとき表示されるのは次のエラーです。
Google Cloud credentials expired or invalid · refresh the gateway token provided via ANTHROPIC_AUTH_TOKEN/ANTHROPIC_CUSTOM_HEADERS and retry · API Error: 401 {"error":"401 Unauthorized: Token has expired", ...}メッセージはGoogle Cloudの認証情報を疑わせますが、この構成で期限切れになっているのはゲートウェイ用のトークンです。ヒント部分もANTHROPIC_AUTH_TOKENやANTHROPIC_CUSTOM_HEADERSを指していて、apiKeyHelperには触れません。
設定ドキュメントとの食い違い
apiKeyHelperの再実行条件は、設定リファレンスに3つ載っています。
apiKeyHelperが再実行される3つの条件
キャッシュ寿命の経過
既定は5分です。
CLAUDE_CODE_API_KEY_HELPER_TTL_MSで変えられます。401か403の応答
Anthropic APIへのリクエスト、またはLLMゲートウェイ経由のリクエストが対象です。
JWTの期限切れ(送信前)
キャッシュ済みの出力がJWTで、発行後に期限が切れていた場合です。v2.1.246以降で、送信前に再実行されます。
後ろの2つは、ヘルパーの出力が送信する認証情報で、ANTHROPIC_AUTH_TOKENが未設定のときに限られます。記載はAnthropic APIへのリクエストに向けたもので、Vertexモードでの扱いは書かれていません。
ここで引っかかるのがJWTの送信前チェックです。Oktaの60分IDトークンはJWTなので、v2.1.246以降なら期限切れを送信前に検知してヘルパーが回りそうに見えます。ところが報告者の環境は2.1.308で、それでも再実行されませんでした。設定リファレンスの条件はAnthropic API向けの記載で、Vertexモードがその外にあることと、辻褄は合います。ただし、JWTチェックがVertexモードで働くかどうかは、公式のページには書かれていません。
issueの報告者は、同じゲートウェイ・同じヘルパー・同じトークンで次の差を確かめています。
| 構成 | 401のあとの挙動 |
|---|---|
CLAUDE_CODE_USE_BEDROCK=1 + CLAUDE_CODE_SKIP_BEDROCK_AUTH=1 | 401のあとの挙動ヘルパーを再実行して再試行し、成功する |
CLAUDE_CODE_USE_VERTEX=1 + CLAUDE_CODE_SKIP_VERTEX_AUTH=1 | 401のあとの挙動ヘルパーを再実行せず、同じトークンで失敗し続ける |
Bedrock構成ではキャッシュの寿命を3600000ミリ秒(トークンの寿命と同じ60分)にしても、401でヘルパーが回って復旧していました。Vertexに移したところ、この復旧が働かなくなったという流れです。
再現手順
報告に載っている最小の再現は、1回目だけ不正なトークンを出すヘルパーを使います。まずsettings.jsonです。
{
"apiKeyHelper": "~/bin/helper.sh",
"env": {
"CLAUDE_CODE_USE_VERTEX": "1",
"CLAUDE_CODE_SKIP_VERTEX_AUTH": "1",
"ANTHROPIC_VERTEX_BASE_URL": "https://gateway.example.com/vertexai",
"ANTHROPIC_VERTEX_PROJECT_ID": "example-project",
"CLOUD_ML_REGION": "global"
}
}ヘルパーは実行のたびにログへ1行残し、初回は不正値、2回目以降は有効なトークンを返します。
#!/bin/bash
echo "$(date +%s)" >> /tmp/helper-runs.log
if [ ! -f /tmp/helper-ran ]; then
touch /tmp/helper-ran
echo invalid-token
else
get-valid-token # 有効なトークンを出す自前のコマンド
ficlaude -p "Reply with just: ok"
wc -l /tmp/helper-runs.log報告では、リクエストは401で失敗し、helper-runs.logは1行のままでした。ヘルパーが再実行されていれば2行になり、2回目で成功するはずです。ゲートウェイは、ベアラートークンを検証するものなら何でも構いません。
なぜ再実行されないのか(報告者の読み)
原因は、報告者が2.1.304のバンドルを読んで推測したものです。公式の説明ではなく、コードを直接確かめたものでもないので、そのつもりで読んでください。
報告者の説明は次のとおりです。
- Vertexモードでステータスが401のエラーは、種類を問わずGoogle認証情報のエラーとして扱われる
- その処理はGCPの認証キャッシュを消し、
gcpAuthRefreshを拒否済みにするだけ - 汎用の401経路(ヘルパーのキャッシュを消す処理)に到達する前に、リトライ判定が終わる
- 再試行は同じキャッシュ済みトークンで、GCP認証の再試行上限(2回)まで繰り返されたあと、冒頭のエラーで終わる
Bedrockでは、先に引き受けるのが認証情報プロバイダーのエラーか403だけです。ゲートウェイの401は汎用経路まで届き、ヘルパーのキャッシュが消えます。これが両構成の差だという説明になっています。
gcpAuthRefreshは救いになりません。この設定が更新するのはGCPの認証情報で、ゲートウェイ構成が使わないと決めたものだからです。
エラー一覧の「Google Cloud credentials expired or invalid」の項にも、対処が2通り載っています。ADCを使う構成にはgcpAuthRefreshのコマンドかgcloud auth application-default loginで再ログインを案内し、CLAUDE_CODE_SKIP_VERTEX_AUTHを使うゲートウェイ構成にはANTHROPIC_AUTH_TOKENかANTHROPIC_CUSTOM_HEADERSのゲートウェイトークンを更新するよう案内しています。このissueの構成はどちらにも合いません。Google認証は省いているので再ログインは無関係で、トークンは環境変数でなくapiKeyHelperが出すため、書き換える先の環境変数がありません。
環境変数の説明も押さえておきます。CLAUDE_CODE_SKIP_VERTEX_AUTHはGoogle認証を省く設定で、LLMゲートウェイ利用が例に挙がっています。CLAUDE_CODE_API_KEY_HELPER_TTL_MSは、apiKeyHelperを使うときに認証情報を更新する間隔をミリ秒で指定します。この記事の回避策は、後者の説明どおりの使い方です。
自分の構成が該当するかの切り分け
次の4点がすべて当てはまる場合に、この記事の現象を疑えます。
CLAUDE_CODE_USE_VERTEX=1とCLAUDE_CODE_SKIP_VERTEX_AUTH=1の両方を設定しているANTHROPIC_VERTEX_BASE_URLがゲートウェイを指している- 資格情報は
apiKeyHelperが出すトークンで、ゲートウェイがその有効期限を検証している - エラーが
API Error: 401で、ゲートウェイ側の応答本文にトークン期限切れや不正の記述がある
3と4が揃わなければ別の問題です。エラー本文にゲートウェイの理由が出ていない401は、ゲートウェイの設定側を疑います。ヘルパーのスクリプト自体が失敗する場合はapiKeyHelperが失敗する原因と対処で扱っています。ADCやキーファイルでVertexに直接つなぐ構成の同名エラーは、Google Cloud credentials expiredの直し方が対象です。
回避策: ヘルパーのキャッシュ寿命を短くする
報告者の回避策は、CLAUDE_CODE_API_KEY_HELPER_TTL_MSを、ヘルパー自身の更新余裕より短くすることです。このヘルパーは残り2分を切ると更新する作りで、TTLには90000(90秒)を選んでいます。
{
"apiKeyHelper": "~/bin/helper.sh",
"env": {
"CLAUDE_CODE_USE_VERTEX": "1",
"CLAUDE_CODE_SKIP_VERTEX_AUTH": "1",
"CLAUDE_CODE_API_KEY_HELPER_TTL_MS": "90000"
}
}考え方は次のとおりです。401で再実行できないなら、トークンが切れる前に定期的にヘルパーを回してしまいます。ヘルパーがトークンの残り時間を見て、足りないときだけ取り直す実装なら、90秒ごとの呼び出しの大半は手元のキャッシュを返すだけで済みます。
トレードオフもあります。
- ヘルパーは必要なときでなく90秒ごとに呼ばれる。IdPや社内CLIを毎回叩く実装だと負荷になる
- TTLよりヘルパーの更新余裕が短いと、切れる寸前のトークンが渡り得る。TTL、更新余裕、トークン寿命の三つを並べて決める
- 利用者ごとの設定ファイルか、ゲートウェイ接続の設定を配る仕組みに、この値を足す必要がある。接続の設定手順はLLMゲートウェイの接続にある
既定のキャッシュ寿命は5分なので、トークンの寿命が60分でヘルパーが適切に更新していれば、切れたトークンを掴む時間はそもそも短いはずです。症状が出やすいのは、報告者のようにBedrock時代の設定を引き継いでTTLを60分に伸ばしていたケースだと考えられます。これは設定の仕組みからの推測で、報告されたのはTTLを伸ばしていた構成だけです。
修正の見通しと追い方
issueは2.1.308での報告で、openのままコメントはまだありません。ラベルはbug、has repro、api:vertex、area:authです。報告者の提案する修正は2案あります。
CLAUDE_CODE_SKIP_VERTEX_AUTHが設定されているとき、401をGCP認証エラーとして分類せず汎用の401経路に流す- あるいはそのGCP側の分岐でも
apiKeyHelperのキャッシュを消す
どちらが採られるかは決まっていません。修正版が出たかは、changelogの該当バージョンでapiKeyHelperとVertexを探して確認できます。issueには関連として、バックグラウンドセッションがゲートウェイ設定を落とす#87255、ゲートウェイトークンを更新するヘルパーをANTHROPIC_CUSTOM_HEADERSに求める#97949、Anthropic API向けの「401でキャッシュを消して再実行」を示す#100514が挙がっています。#87255の症状はNo LLM gatewayの直し方に対応します。
修正が入ったあとは、上の回避策のTTLを元に戻して、再現手順のhelper-runs.logが2行になることで確かめられます。
まとめ
Vertexモード + CLAUDE_CODE_SKIP_VERTEX_AUTHの構成では、401が出てもapiKeyHelperは再実行されません。当面は、CLAUDE_CODE_API_KEY_HELPER_TTL_MSをヘルパーの更新余裕より短くして、切れる前に取り直すのが報告されている回避策です。ゲートウェイの構成全体はLLMゲートウェイの比較にまとめています。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。