Codex CLI 401 Unauthorized: 検証済み9つの原因とそっくりさん

Codex CLI 0.146.0 の認証は9通りの壊れ方をするが、すべてが本物の401とは限らない。コードではなく本文を読め。キー未送信、キー拒否、改行。

Codex CLI 401 Unauthorized: 検証済み9つの原因とそっくりさん

TL;DR: Codex CLI の認証は9通りの明確に異なる形で壊れ、そのうち一部だけが本物の 401 として現れます。だからこそステータスコードは出力の中で最も役に立たない部分なのです。原因を特定するのはメッセージ本文です。Missing bearer or basic authentication in header は何も送られなかったという意味で、驚くべきことに、デフォルトプロバイダーでは OPENAI_API_KEY をエクスポートしても効きません。Incorrect API key provided はキーが届いたうえで拒否されたという意味です。You didn't provide an API key はヘッダーが途中で落ちたという意味で、ほぼ必ず値末尾の改行が原因です。ローカルの Missing environment variable エラーはそもそも 401 ではなく、/v1 を失った base_url からの 404 も同様です。以下のすべてのケースは 2026-07-30 に Codex CLI 0.146.0 で再現しました。

30秒診断

3つのチェックを、この順序で。多くの人は最初の1つを飛ばし、実は問題なかったキーの再発行に20分を費やします。

チェックコマンド何がわかるか
1. そもそもリクエストは発生したか?出力に Missing environment variable があるか探すあれば、Codex はネットワーク呼び出しを一度も行っていない。設定が未設定または空の変数を指している。
2. 401 の本文は何と言っているか?401 Unauthorized: の後のテキストを読む3つの明確に異なるメッセージ、3つの明確に異なる原因。下の表を参照。
3. キーはそもそも通るか?curl -s -o /dev/null -w "%{http_code}" -X POST https://api.ofox.ai/v1/responses -H "Authorization: Bearer $YOUR_KEY" -H "Content-Type: application/json" -d '{"model":"openai/gpt-5.5","input":"hi","max_output_tokens":16}'200 ならキーは良好で、問題は Codex 設定側。401 ならキー自体が問題。

ステップ3で早めに指摘しておくべき落とし穴が1つあります。ネットのトラブルシューティング情報の半分がここを間違えているからです。アグリゲーターではキーチェックに /v1/models を使わないこと。 2026-07-30 に検証したところ、https://api.ofox.ai/v1/models は偽のキーで送っても、Authorization ヘッダーがまったくなくても、カタログ全体とともに 200 を返します。モデルカタログは公開情報です。OpenAI 自身の api.openai.com/v1/models はキーなしで 401 を返し、そこからこの習慣が来ているのですが、その習慣は通用しません。実際に推論を実行するエンドポイントを使ってください。

いつ直すか、いつ切り替えるか、いつやめるか

認証失敗は、どれに当たっているか分かっていれば直すのは安上がりで、当てずっぽうだと高くつきます。おおまかな指針:

  • 直すべきは、メッセージが具体的な原因を名指しているとき: Missing environment variableIncorrect API key provided、あるいはリフレッシュトークンに言及する何か。これらには決定論的な1ステップの修正があり、それぞれ2分以内で済みます。
  • 認証経路を切り替えるべきは、ChatGPT ログインフローで3回目の試行に入っているとき。API キー経路は可動部が少なく(リフレッシュトークンなし、ブラウザ往復なし、8日のリフレッシュ窓なし)、何かを自動化するなら、コンテナ再起動を生き延びる唯一の経路です。
  • 止めて別の何かを確認するべきは、401 ではなく 404 が出たとき、または CLI が設定エラーで起動を拒否するとき。これらは認証問題ではなく、いくらキーをローテーションしても動きません。認可ではなく使用量上限に言及するエラーも同様で、その経路は Codex の週次上限が枯渇 で扱っています。

キー再発行が正しい最初の一手となる唯一のケースは、Incorrect API key provided で、エラーに表示されるマスク付きサフィックスが、あなたが使っているつもりのキーと一致している場合です。サフィックスが一致しないなら、それはキーの問題ではなく設定の問題で、新しいキーも同じように失敗します。

401 を読む: 3つのメッセージ、3つの原因

これが中核の表です。各行はライブエンドポイントに対して再現しました。

メッセージ本文実際に起きたことどこから来るか修正
Missing bearer or basic authentication in headerリクエストに認証情報が付いていなかったログインしていないデフォルト OpenAI プロバイダー、または OPENAI_API_KEY だけをエクスポートした状態printenv OPENAI_API_KEY | codex login --with-api-key
Incorrect API key provided: sk-proj-****7890ヘッダーは届いたが、サーバーが値を拒否したauth.json 内の誤った・失効した・別アカウントのキーキーを再発行するか、ログアウトして正しいキーでログインする
Invalid or expired API key上と同じ、ゲートウェイ側の表現env_key の値が誤っているか引用符で囲まれているカスタムプロバイダー設定済みかどうかだけでなく、変数の正確なバイト列を確認する
You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer authヘッダーが不正形式で落とされたキー値に改行が埋め込まれている変数から末尾の改行を除去する
Your access token could not be refreshed...ChatGPT OAuth のリフレッシュが失敗した期限切れ・再利用・失効したリフレッシュトークンcodex logout してから再度サインインする
Missing environment variable: 'X'401 ではない。 リクエストは行われていないenv_key が未設定または空の変数を名指ししているCodex を起動するシェルで変数をエクスポートする

Codex CLI の401診断フロー: まずローカルの Missing environment variable エラーを確認し、次にメッセージ本文を読む。本文はキー未送信、キー拒否、改行によるヘッダー脱落に分岐する

テスト実行から得られた、出力を読みやすくする2つの詳細があります。デフォルトの OpenAI プロバイダーでは、Codex はまず wss://api.openai.com/v1/responses に対して WebSocket トランスポートを試み、5回リトライし、その後 HTTPS にフォールバックしてさらに5回リトライします。そのため単一の認証失敗が、本当のメッセージの前におよそ10行のエラーを生みます。カスタムプロバイダーでは WebSocket トランスポートはオフで(codex doctorsupports websockets: false と報告)、リトライループは1つで済み、失敗もすっきりします。Reconnecting... 4/5 の壁を眺めているなら、一番下までスクロールしてください。最後の行が重要な行です。

原因1: OPENAI_API_KEY をエクスポートして、それで十分だと思い込んだ

これは単独で最もよくある原因で、直感に反する度合いが高いのでトップに置くに値します。

export OPENAI_API_KEY="sk-proj-..."
codex exec "say hi"
# ERROR: unexpected status 401 Unauthorized: Missing bearer or basic
# authentication in header, url: https://api.openai.com/v1/responses

サーバーが何と言ったか注目してください。Missing bearer です。「キーが間違っている」ではありません。何も送られなかったのです。Codex 0.146.0 のデフォルトプロバイダーは環境からではなく $CODEX_HOME/auth.json から認証情報を読みます。変数を設定しても何も変わりません。

単一変数による証明: まったく同じ無効なキーを、環境ではなく auth.json に書き込むと、メッセージが変わります。

echo "sk-proj-invalidkeyfortesting1234567890" | codex login --with-api-key
codex exec "say hi"
# ERROR: unexpected status 401 Unauthorized: Incorrect API key provided:
# sk-proj-**************************7890 ... auth error code: invalid_api_key

同じキー、異なるメッセージ。最初の実行は一度も送らず、2回目は送って拒否されました。この違いこそが診断のすべてです。

Codex が実際に探す場所にキーを書き込んでください:

printenv OPENAI_API_KEY | codex login --with-api-key

環境変数は確かに機能しますが、カスタムプロバイダーの env_key フィールドを通じてのみです。これは別の仕組みで、後述します。

原因2: 古い —api-key フラグを使った

2026年半ば以前に書かれたチュートリアルに従った場合:

codex login --api-key "sk-proj-..."
# The --api-key flag is no longer supported. Pipe the key instead,
# e.g. `printenv OPENAI_API_KEY | codex login --with-api-key`.

メッセージは明快ですが、何も書き込まずに終了し、セットアップスクリプトの中では出力がスクロールで流れてしまいます。すると次のコマンドが Missing bearer で失敗し、キーのせいにされます。auth.json が存在し、期待どおりの内容を含んでいるか確認してください:

cat ~/.codex/auth.json
# {
#   "auth_mode": "apikey",
#   "OPENAI_API_KEY": "sk-proj-..."
# }

原因3: env_key が未設定の変数を指している (401 ではない)

カスタムプロバイダーブロックを使うと、Codex は名指しした環境変数からキーを読みます:

model = "openai/gpt-5.5"
model_provider = "ofox"

[model_providers.ofox]
name = "Ofox"
base_url = "https://api.ofox.ai/v1"
env_key = "OFOX_API_KEY"
wire_api = "responses"

OFOX_API_KEY が未設定なら、401 は得られません。ローカルエラーが出て、ネットワーク呼び出しはまったく行われません:

ERROR: Missing environment variable: `OFOX_API_KEY`.

空文字列もまったく同じエラーを生みます。これはソースと一致します。model-provider-info/src/lib.rs は使用前に !v.trim().is_empty() で変数をフィルタします。したがって Codex にとっては、export OFOX_API_KEY="" と一度もエクスポートしないことは同じです。

これは launchd、systemd、Docker で最も痛い形で噛みつきます。Codex を起動するシェルが、変数をエクスポートしたシェルとは別だからです。

原因4: キー値の中の改行

このセットの中で最もいやらしいものです。エラーが、printenv で自分の目で見えるキーを提供していないと非難してくるからです。

export OFOX_API_KEY="$(cat ~/keys/ofox.txt)"   # file ends with a newline
codex exec "hi"
# ERROR: unexpected status 401 Unauthorized: You didn't provide an API key.
# You need to provide your API key in an Authorization header using Bearer
# auth (i.e. Authorization: Bearer YOUR_KEY). [ofox.ai]

ヘッダーは中に改行を入れて組み立てられ、その場で捨てられました。サーバーは本当に認証情報を一度も見ておらず、そのメッセージは正確なのです。ただ、まるであなたが何も設定し忘れたかのように聞こえるだけです。

ここで原因ではないものを知っておく価値があります。それは明白な容疑者でありながら無実だからです。末尾のスペースは問題ありません。 export OFOX_API_KEY="$REAL " で検証したところ、リクエストは成功しました。ただし Codex があなたのために整えているわけではありません。api_key() 内の trim() は空判定のみで、返される値は末尾のスペースを含む生のものです。何か下流がそれを許容しているのです。対照的に改行はヘッダーを完全に壊します。いずれにせよ、迷子のスペース探しに時間を使わないでください。

一方、引用符で囲まれた値は失敗し、異なる表現になります:

export OFOX_API_KEY='"sk-..."'   # literal quote characters in the value
# ERROR: unexpected status 401 Unauthorized: Invalid or expired API key

これは、引用符を残す素朴な export $(cat .env | xargs).env ファイルを読み込んだときによく起こります。

エクスポート時に問題のバイトを除去し、長さを確認してください:

export OFOX_API_KEY="$(tr -d '\n\r"' < ~/keys/ofox.txt)"
printf '%s' "$OFOX_API_KEY" | wc -c   # confirm the byte count matches the key length

原因5: プロバイダーブロックに env_key がまるごと欠けている

9つの中で最も静かな失敗です。上の設定から1行を削除します:

[model_providers.ofox]
name = "Ofox"
base_url = "https://api.ofox.ai/v1"
wire_api = "responses"
# env_key line deleted

Codex は文句を言いません。auth.json の認証情報(多くの人にとっては OpenAI キー)にフォールバックし、それをゲートウェイに送ります。ゲートウェイはそれを拒否します:

ERROR: unexpected status 401 Unauthorized: Invalid or expired API key,
url: https://api.ofox.ai/v1/responses

あなたの環境変数は正しく設定されています。あなたのキーは有効です。エラーはキーが無効だと言いますが、それは別のキーが送られたからです。同じシェルで両方向を検証しました。env_key があればリクエストは通常の補完を返し、行を削除すると 401 になります。

逆もまた知っておく価値があり、これは朗報です。env_keyある場合、それは auth.json に勝ちます。auth.json にわざと偽のキーを保存し、環境変数に有効なものを入れて検証したところ、リクエストは成功しました。カスタムプロバイダーを設定する前にログアウトする必要はありません。

ゲートウェイプロバイダーをゼロから設定するなら、検証済みの完全なブロックが Codex CLI カスタムモデルプロバイダー にあり、ファイル内のすべてのキーは config.toml リファレンス で文書化されています。

原因6: ChatGPT ログイン経路が期限切れになった

API キーではなく ChatGPT サブスクリプションでサインインした場合、失敗はまったく異なって見えます:

ERROR: Your access token could not be refreshed. Please log out and sign in again.

周辺のログ行にあるエンドポイントに注目してください。api.openai.com ではなく wss://chatgpt.com/backend-api/codex/responses です。2つのログインモードは異なるバックエンドと話すので、これは自分が実際にどちらにいるかを素早く見分ける方法です。

Codex 0.146.0 はここで5つのうち1つを表示し、それらは互換ではありません。タグ rust-v0.146.0login/src/auth/manager.rs より:

亜種実際の意味
...because your refresh token has expired本物の期限切れ。再度サインインする。
...because your refresh token was already used2つのクライアントが同じ auth.json で競合した。コピーした設定か、共有コンテナイメージ。
...because your refresh token was revokedサーバー側でセッションが破棄された。多くはパスワード変更や別所でのサインアウトによる。
...could not be refreshed. (理由なし)リフレッシュエンドポイントが分類不能な何かを返した。再度サインインする。
...because you have since logged out or signed in to another account保存されたアカウントがアクティブなものと一致しなくなった。

「already used」の亜種が人を驚かせるものです。リフレッシュトークンは1回限りの使用なので、auth.json を Docker イメージに焼き込んだり、~/.codex を2台のノートPC間で同期したりすると、先にリフレッシュしたマシンでは動き、もう一方では壊れる認証構成ができあがります。同じソースファイルは TOKEN_REFRESH_INTERVAL を8日に設定しているので、1週間以上アイドルだったマシンは次回実行時に先行リフレッシュを試み、たいていはそのときに競合が表面化します。

修正は1つで、それは無骨なものです:

codex logout
codex login          # browser flow
# or, for anything automated:
printenv OPENAI_API_KEY | codex login --with-api-key

無人環境では API キー経路を優先してください。同期がずれるリフレッシュのセマンティクスがありません。

原因7: codex login status がすべて問題なしと言った

これは2通りの異なる形で嘘をつき、両方とも再現しました。

期限切れの ChatGPT トークンを含む手作りの auth.json では、あらゆるリクエストが上記のリフレッシュエラーで失敗する一方で:

codex login status
# Logged in using ChatGPT

そしてカスタムプロバイダー下では、status は auth.json に置かれたキーを報告しますが、それはリクエストに使われているキーとはまったく別物です:

codex login status
# Logged in using an API key - sk-proj-***n-999

どちらの出力もファイルの中身を説明しているだけです。どちらもネットワークチェックを行いません。「認証情報が保存されているか」に答えるために使い、「自分の認証が機能するか」には決して使わないでください。後者には、30秒診断の curl を実行するか、単に codex exec "hi" を実行して最後の行を読んでください。

ここでは codex doctor の方が有用です。その Configuration セクションは、どの config.toml が読み込まれ、パースできたか、認証ストレージモード、どの認証環境変数を認識できるかを示します。Connectivity セクションは、アクティブなプロバイダー、wire API、WebSocket トランスポートが適用されるか、エンドポイントが到達可能かを報告します。依然として認証情報の検証はしませんが、CODEX_HOME が設定されているときに驚くほど頻繁な根本原因である「Codex があなたが編集してきたものとは別の設定ファイルを読んでいる」ことを、1秒以内に教えてくれます。

原因8: auth.json が壊れている (401 ではない)

まれですが、認証問題にはまったく見えないエラーを生むので、まさにそれゆえに時間を食います。切り詰められた、または手編集された auth.json:

codex exec "hi"
# EOF while parsing a value at line 2 column 0

認証への言及なし、HTTP ステータスなし、ファイルパスなし。これは codex login が中断されたとき、部分的に同期されたファイル、または閉じ括弧を落とした手編集の後に起こります。ファイルは直接調べられるほど小さいものです:

python3 -m json.tool ~/.codex/auth.json > /dev/null && echo "valid JSON"

パースできないなら、削除して再度ログインしてください。回収に値するものは何もありません。貼り直せる API キーか、再発行される OAuth トークンのどちらかです。

原因9: Codex が編集したものとは別の設定を読んでいる

CODEX_HOMEconfig.tomlauth.json の両方を移動させます。シェルプロファイル、ラッパースクリプト、またはあなたのために Codex を起動したツールでこれが設定されていると、~/.codex/config.toml に加えたすべての編集は、Codex が一度も開かないファイルへ行ってしまいます。症状は、直したはずの変更を生き延びる 401 です。

codex doctor はこれに1行で答えます。その state セクションの下:

CODEX_HOME  /private/tmp/codex401/home7 (dir)

そして Configuration の下:

config.toml  /private/tmp/codex401/home7/config.toml
config.toml parse  ok

そのパスがあなたが編集してきたファイルでないなら、キーのデバッグはやめてください。

同じコマンドはこの問題の第2バージョンにも印を付けます。Codex がグローバルにもローカルにもインストールされているマシンで、doctor は次のように表示します:

✗ install   npm install -g @openai/codex would update a different install
✗ updates   update would target a different npm install

文字どおり読むと、更新コマンドが実行中のバイナリとは別のパッケージルートを対象にしているという意味です。したがって認証修正を取り込むはずのアップグレードが、実行中のコピーを手つかずのままにしうるのです。バージョンアップが効かなかったと結論づける前に、解決しておく価値があります。

終了コードが教えてくれない1つのこと

この記事のすべての失敗モードは 1 で終了します。認証情報なし、環境変数の欠落、設定パースエラー、壊れた auth.json、拒否されたキー、すべて 1 です。Codex をスクリプトで包んで終了ステータスで分岐しているなら、「キーが間違っている」と「設定ファイルにタイプミスがある」を区別できません。代わりに stderr をキャプチャしてメッセージテキストでマッチさせてください:

out=$(codex exec "ping" 2>&1) || {
  case "$out" in
    *"Missing environment variable"*) echo "config points at an unset variable" ;;
    *"Missing bearer"*)               echo "no credential was sent" ;;
    *"Incorrect API key"*|*"Invalid or expired API key"*) echo "credential rejected" ;;
    *"could not be refreshed"*)       echo "ChatGPT session expired, log in again" ;;
    *) echo "other failure: $out" ;;
  esac
}

401 に見えて実はそうでない2つのもの

/v1 が欠けた base_url は 401 ではなく 404 を返します:

ERROR: unexpected status 404 Not Found: 404 page not found,
url: https://api.ofox.ai/responses

期待したパスセグメントが欠けた URL で 404 page not found を見たら、URL を直し、認証情報を見るのはやめてください。

wire_api = "chat" はどのリクエストよりも前、設定読み込み時に失敗します:

Error loading config.toml: `wire_api = "chat"` is no longer supported.
How to fix: set `wire_api = "responses"` in your provider config.
More info: https://github.com/openai/codex/discussions/7782

chat の値は削除されました。enum には Responses バリアントが1つ残っているだけです。Codex を向けるどのゲートウェイも、Responses 互換のエンドポイントを公開している必要があります。これは古い設定から移行する人を引っかけ、起動時にプロセスを殺すため、ときどき「アップグレード後に認証が壊れた」として届け出られます。壊れていません。この制約を生き延びるモデルについては OpenCode vs Codex CLI で詳しく。

第3のニアミス: 企業プロキシの背後にいる場合、失敗モードはたいてい 401 ではなく TLS または接続エラーで、修正はまったく別のものです。その経路は Codex CLI を企業プロキシの背後で で扱っています。

認証モード別の修正

同じ症状でも、認証方法に応じて異なる修正が必要です。これがブックマークすべき表です。

認証モード認証情報の在り処最も可能性の高い 401 の原因修正
ChatGPT サブスクリプションauth.json の OAuth トークンリフレッシュトークンの期限切れ、またはマシン間での再利用codex logout してから codex login
OpenAI API キーauth.jsonOPENAI_API_KEY フィールドキーが一度も書き込まれていない(古い --api-key フラグ、または環境へのエクスポートのみ)printenv OPENAI_API_KEY | codex login --with-api-key
カスタムゲートウェイプロバイダーenv_key が名指しする環境変数起動シェルで変数が未設定、値の中の改行、または設定から env_key 行が欠落正しいシェルでエクスポート、改行を除去、行の存在を確認
自動化またはコンテナ環境変数、実行時に注入1回限りのリフレッシュトークンを含む焼き込み auth.jsonAPI キー経路を使い、キーを env var として注入、イメージに auth.json を決して同梱しない

コンテナと CI のセットアップに特化して: ~/.codex から何もマウントせず、変数をコンテナ環境で設定し、env_key を持つカスタムプロバイダーブロックを使ってください。この組み合わせにはリフレッシュ状態も、陳腐化するファイルもありません。

よくある失敗パターンと、それぞれが実際に何を表示するか

上記のすべてを1つのグリッドに。2026-07-30 に Codex CLI 0.146.0、macOS で再現。デフォルトプロバイダー行は api.openai.com、カスタムプロバイダー行は api.ofox.ai に対して。

#設定結果出力
1どこにも認証情報なし401Missing bearer or basic authentication in header
2OPENAI_API_KEY エクスポート済み、デフォルトプロバイダー401Missing bearer or basic authentication in header (#1 と同一)
3--with-api-key 経由で無効なキーを書き込み401Incorrect API key provided: sk-proj-****7890auth error code: invalid_api_key
4codex login --api-key (古いフラグ)終了The --api-key flag is no longer supported
5カスタムプロバイダー、env_key 変数が未設定ローカルエラーMissing environment variable: 'OFOX_API_KEY'
6カスタムプロバイダー、env_key 変数が空文字列ローカルエラー#5 と同一
7カスタムプロバイダー、無効なキー401Invalid or expired API key
8カスタムプロバイダー、リテラルの引用符で囲まれたキー401Invalid or expired API key
9カスタムプロバイダー、末尾スペース付きのキー200リクエスト成功、末尾スペースは下流で許容される
10カスタムプロバイダー、末尾改行付きのキー401You didn't provide an API key...
11カスタムプロバイダー、設定から env_key 行を削除401Invalid or expired API key (代わりに auth.json のキーが送られる)
12auth.json に偽のキー、env_key に有効なキー200env_key が優先される
13ChatGPT auth.json、陳腐化したリフレッシュトークンリフレッシュエラーYour access token could not be refreshed...
14base_url/v1 が欠落404404 page not found
15wire_api = "chat"設定エラーwire_api = "chat" is no longer supported

9行目と12行目は、可能性を排除することで時間を節約する2つです。空白を探し回っていたり、ゲートウェイを設定する前にログアウトが必要だと思い込んでいたりするなら、どちらも行き止まりです。

認証情報を直せないとき: 今すぐ使える代替策

タイプミスではなく認証経路そのものが障害になっているなら、いくつか選択肢があります。

選択肢何を解決するかトレードオフ
ChatGPT ログインの代わりに API キーリフレッシュトークン、ブラウザフロー、8日のリフレッシュ窓を取り除くサブスクリプションでカバーされる代わりにトークン単位で課金
env_key を持つゲートウェイプロバイダーキー1つ、変数1つ、ログインステップなしでコンテナと CI で動くResponses 互換のエンドポイントが必要なので、モデルごとのサポートは異なる
手動フォールバックとしての第2プロバイダーブロック1つの認証情報が死んだとき --config model_provider=... で切り替えられる自動ではなく手動
別のエージェントOpenCode はログインステップなしで環境変数から直接プロバイダー認証情報を読む別のツール、別のデフォルト、直接対決の比較を参照

自動化セットアップで最も可動部を取り除くのがゲートウェイ経路です。期限切れになるログインステップがないからです。Ofox はその1つとして機能します。/v1/responses を公開しているので wire_api = "responses" の要件を満たし、単一の OFOX_API_KEY が複数ベンダーのモデルに届きます。つまり1つの上流の認証情報問題で、動かすものが何もなくなる事態にはなりません。サポートはゲートウェイ単位ではなくモデル単位なので、コミットする前に使いたいモデルを確認してください。セットアップブロックは原因3で示したもので、モデル ID 付きの長い版は Codex CLI API 設定ガイド にあります。

Claude Code から来て両者の認証の使い勝手を比較しているなら、Claude Code から Codex への移行 が、何が引き継がれ、何を再設定しなければならないかを扱っています。

これを再発させないために

繰り返しのインシデントのほとんどを防ぐいくつかの習慣:

カタログ呼び出しではなく実際のリクエストで検証する。 /v1/models の ping ではなく、これをセットアップスクリプトに入れてください:

code=$(curl -s -o /dev/null -w "%{http_code}" -X POST https://api.ofox.ai/v1/responses \
  -H "Authorization: Bearer $OFOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"openai/gpt-5.5","input":"ping","max_output_tokens":16}')
[ "$code" = "200" ] || echo "auth check failed: HTTP $code"

改行に対してエクスポートの時点で守る、後からではなく:

export OFOX_API_KEY="$(tr -d '\n\r' < ~/keys/ofox.txt)"

auth.json をイメージに同梱したり、マシン間で同期したりしない。 1回限りのリフレッシュトークンが、設計上それを競合にします。代わりに環境を通じてキーを注入してください。

設定変更のたびに codex doctor を実行する。 誤った CODEX_HOME と config-did-not-parse のケースを約1秒で捕まえます。これらは、まったく問題のないキーを疑わせる最も可能性の高い2つの失敗です。

出典