Codex CLI 401 Unauthorized: 9 проверенных причин и обманки

Аутентификация Codex CLI 0.146.0 ломается девятью способами, и не все дают буквальный 401. Читайте текст, а не код: ключ не отправлен или отклонён.

Codex CLI 401 Unauthorized: 9 проверенных причин и обманки

Кратко: Аутентификация Codex CLI ломается девятью различными способами, и лишь некоторые из них приходят как буквальный 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, как и 404 от base_url, потерявшего свой /v1. Каждый случай ниже был воспроизведён на Codex CLI 0.146.0 2026-07-30.

Диагностика за 30 секунд

Три проверки, в этом порядке. Большинство людей пропускают первую и тратят двадцать минут на перевыпуск ключа, который был в порядке.

ПроверкаКомандаЧто она говорит
1. А запрос вообще был?Поищите Missing environment variable в выводеЕсли есть, Codex так и не сделал сетевой вызов. Ваша конфигурация указывает на переменную, которая не задана или пуста.
2. Что говорит тело 401?Прочитайте текст после 401 Unauthorized:Три разных сообщения, три разные причины. См. таблицу ниже.
3. Работает ли ключ вообще?curl -s -o /dev/null -w "%{http_code}" -X POST https://api.ofox.io/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, о которой стоит сказать заранее, потому что половина советов по устранению неполадок в интернете ошибается на этом: не используйте /v1/models как проверку ключа на агрегаторе. Проверено 2026-07-30, https://api.ofox.io/v1/models возвращает 200 с полным каталогом при отправке с фиктивным ключом, и даже вообще без заголовка Authorization. Каталог моделей публичный. Собственный api.openai.com/v1/models от OpenAI действительно возвращает 401 без ключа, откуда и появилась эта привычка, но привычка не переносится. Используйте эндпоинт, который реально выполняет инференс.

Когда чинить, когда переключаться, а когда остановиться

Сбои аутентификации дёшево чинить, когда вы знаете, какой именно у вас случай, и дорого, когда вы гадаете. Грубое правило:

  • Чините, когда сообщение называет конкретную причину: Missing environment variable, Incorrect API key provided или что-либо про токен обновления. У них детерминированные исправления в один шаг, каждое занимает меньше двух минут.
  • Смените путь аутентификации, когда вы уже на третьей попытке разобраться с потоком входа через ChatGPT. У пути с API-ключом меньше движущихся частей (нет токенов обновления, нет захода в браузер, нет 8-дневного окна обновления), и если вы что-то автоматизируете, это единственный путь, переживающий перезапуск контейнера.
  • Остановитесь и проверьте что-то другое, если вы получаете 404 вместо 401 или если CLI отказывается запускаться с ошибкой конфигурации. Это не проблемы аутентификации, и никакая ротация ключей их не сдвинет. То же самое, если ошибка упоминает лимит использования, а не авторизацию; этот путь разобран в статье Codex weekly limit drained.

Единственный случай, когда перевыпуск ключа, это правильный первый шаг, это Incorrect API key provided с замаскированным суффиксом в ошибке, совпадающим с ключом, который вы думаете используете. Если суффикс не совпадает, у вас проблема с конфигурацией, а не с ключом, и новый ключ упадёт точно так же.

Читаем 401: три сообщения, три причины

Это основная таблица. Каждая строка воспроизведена против живого эндпоинта.

Тело сообщенияЧто на самом деле произошлоОткуда оно приходитИсправление
Missing bearer or basic authentication in headerК запросу не были прикреплены учётные данныеПровайдер OpenAI по умолчанию без входа, или только с экспортированным OPENAI_API_KEYprintenv 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...Обновление OAuth ChatGPT не удалосьИстёкший, повторно использованный или отозванный токен обновленияcodex logout, затем войти снова
Missing environment variable: 'X'Не 401. Запрос не был сделанenv_key называет переменную, которая не задана или пустаЭкспортируйте переменную в той оболочке, которая запускает Codex

Схема диагностики 401 в Codex CLI: сначала проверьте локальную ошибку Missing environment variable, затем прочитайте тело сообщения, которое разбивается на ключ не отправлен, ключ отклонён и заголовок потерян из-за переноса строки

Две детали из тестовых прогонов, которые упрощают чтение вывода. На провайдере OpenAI по умолчанию Codex сначала пробует транспорт WebSocket против wss://api.openai.com/v1/responses, повторяет пять раз, затем откатывается на HTTPS и повторяет ещё пять раз, так что одиночный сбой аутентификации производит примерно десять строк ошибок до настоящего сообщения. На кастомном провайдере транспорт WebSocket выключен (codex doctor сообщает supports websockets: false), поэтому вы получаете один цикл повторов и более чистый сбой. Если вы смотрите на стену из 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

Тот же ключ, другое сообщение. Первый прогон его вообще не отправил; второй отправил и получил отклонение. Эта разница, это и есть вся диагностика.

Запишите ключ туда, где 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.io/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() перед использованием. Так что export OFOX_API_KEY="" и вовсе не экспортировать её, это для Codex одно и то же.

Сильнее всего это кусается в 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 не чистит его за вас: trim() в api_key(), это только проверка на пустоту, и значение, которое он возвращает, сырое, вместе с завершающим пробелом. Что-то ниже по цепочке его терпит. Перенос строки, напротив, полностью ломает заголовок. В любом случае не тратьте время на охоту за случайными пробелами.

Значение в кавычках, с другой стороны, всё же падает, с другой формулировкой:

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

Это распространено, когда .env-файл загружается наивным export $(cat .env | xargs), сохраняющим кавычки.

Уберите проблемные байты в момент экспорта и подтвердите длину:

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

Самый тихий сбой из девяти. Удалите одну строку из конфигурации выше:

[model_providers.ofox]
name = "Ofox"
base_url = "https://api.ofox.io/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.io/v1/responses

Ваша переменная окружения задана правильно. Ваш ключ действителен. Ошибка говорит, что ключ недействителен, потому что был отправлен другой ключ. Проверено обоими способами в одной оболочке: с наличием env_key запрос возвращает нормальное завершение, с удалённой строкой даёт 401.

Обратное тоже стоит знать, и это хорошая новость: когда env_key присутствует, он выигрывает у auth.json. Проверено с намеренно фиктивным ключом в auth.json и действительным в переменной окружения, запрос удался. Вам не нужно выходить перед настройкой кастомного провайдера.

Если вы настраиваете провайдер-шлюз с нуля, полный проверенный блок находится в Codex CLI custom model providers, и каждый ключ в файле задокументирован в справочнике по config.toml.

Причина 6: путь входа через ChatGPT истёк

Если вы вошли через подписку ChatGPT, а не через API-ключ, сбой выглядит совершенно иначе:

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

Обратите внимание на эндпоинт в окружающих строках лога: wss://chatgpt.com/backend-api/codex/responses, а не api.openai.com. Два режима входа общаются с разными бэкендами, это быстрый способ понять, на каком вы на самом деле.

Codex 0.146.0 печатает здесь один из пяти вариантов, и они не взаимозаменяемы. Из login/src/auth/manager.rs на теге rust-v0.146.0:

ВариантЧто это означает на практике
...because your refresh token has expiredНастоящее истечение. Войдите снова.
...because your refresh token was already usedДва клиента столкнулись на одном 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”, это тот, что удивляет людей. Токены обновления одноразовые, поэтому зашивание auth.json в образ Docker или синхронизация ~/.codex между двумя ноутбуками даёт вам настройку аутентификации, которая работает на той машине, что обновляется первой, и ломается на другой. Тот же исходный файл устанавливает TOKEN_REFRESH_INTERVAL в 8 дней, так что машина, простоявшая больше недели, попытается упреждающе обновиться при следующем запуске, и обычно именно тогда конфликт всплывает.

Исправление одно, и оно грубое:

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

Для автономных окружений предпочитайте путь с API-ключом. У него нет семантики обновления, которая может рассинхронизироваться.

Причина 7: codex login status сказал вам, что всё в порядке

Он врёт, двумя разными способами, и оба были воспроизведены.

С вручную собранным auth.json, содержащим просроченный токен ChatGPT, каждый запрос падал с ошибкой обновления выше, при этом:

codex login status
# Logged in using ChatGPT

А под кастомным провайдером status сообщает ключ, лежащий в auth.json, который вообще не используется для запросов:

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

Оба вывода описывают содержимое файла. Ни один не выполняет сетевую проверку. Используйте их, чтобы ответить на вопрос “хранятся ли учётные данные”, а не “работает ли моя аутентификация”. Для последнего запустите curl из диагностики за 30 секунд или просто запустите codex exec "hi" и прочитайте последнюю строку.

codex doctor здесь полезнее. Его секция Configuration показывает, какой config.toml был загружен и удалось ли его разобрать, режим хранения аутентификации, и какие переменные окружения аутентификации он видит; его секция Connectivity сообщает активного провайдера, wire API, применяется ли транспорт WebSocket и доступен ли эндпоинт. Он всё ещё не валидирует учётные данные, но в течение секунды скажет вам, читает ли Codex другой файл конфигурации, чем тот, который вы редактировали, что на удивление частая первопричина, когда задан CODEX_HOME.

Причина 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_HOME переносит и config.toml, и auth.json. Если он задан в вашем профиле оболочки, в скрипте-обёртке или инструментом, который запустил Codex за вас, каждая правка ~/.codex/config.toml уходит в файл, который Codex никогда не открывает. Симптом, это 401, переживающий изменения, которые должны были его исправить.

codex doctor отвечает на это одной строкой, в секции состояния:

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

и в разделе Configuration:

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

Если этот путь не тот файл, который вы редактировали, прекратите отлаживать ключ.

Та же команда отмечает вторую разновидность этой проблемы. На машине, где Codex установлен и глобально, и локально, doctor печатает:

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

В буквальном прочтении это означает, что команда обновления нацелена на другой корень пакета, чем бинарник, который вы запускаете, так что обновление, задуманное для подхвата исправления аутентификации, может оставить запущенную копию нетронутой. Стоит разрешить это, прежде чем делать вывод, что повышение версии не помогло.

Одно, о чём коды выхода вам не скажут

Каждый режим сбоя в этой статье завершается с кодом 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, но им не являются

base_url без своего /v1 даёт вам 404, а не 401:

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

Если вы видите 404 page not found с URL, где не хватает ожидаемого вами сегмента пути, исправьте 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 было удалено; в перечислении осталась единственная вариация Responses. Любой шлюз, на который вы направляете Codex, должен выставлять Responses-совместимый эндпоинт. Это подставляет людей, мигрирующих со старых конфигураций, и поскольку это убивает процесс на старте, иногда это записывают как “аутентификация сломалась после обновления”. Она не ломалась. Подробнее о том, какие модели переживают это ограничение, в OpenCode vs Codex CLI.

Третий близкий промах: если вы за корпоративным прокси, режим сбоя обычно ошибка TLS или соединения, а не 401, и исправление здесь совсем другое. Этот путь разобран в Codex CLI behind a corporate proxy.

Исправления по режимам аутентификации

Один и тот же симптом требует разного исправления в зависимости от того, как вы аутентифицируетесь. Это таблица, которую стоит сохранить в закладки.

Режим аутентификацииГде живут учётные данныеНаиболее вероятная причина 401Исправление
Подписка ChatGPTOAuth-токены в auth.jsonТокен обновления истёк или использован повторно на разных машинахcodex logout, затем codex login
API-ключ OpenAIПоле OPENAI_API_KEY в auth.jsonКлюч так и не записан (старый флаг --api-key или только экспорт в окружение)printenv OPENAI_API_KEY | codex login --with-api-key
Кастомный провайдер-шлюзПеременная окружения, названная в env_keyПеременная не задана в запускающей оболочке, перенос строки в значении, или строка env_key отсутствует в конфигурацииЭкспортируйте в правильной оболочке, уберите переносы строк, подтвердите наличие строки
Автоматизированный или контейнерПеременная окружения, внедрённая во время выполненияЗашитый auth.json с одноразовыми токенами обновленияИспользуйте путь с API-ключом, внедряйте ключ как переменную окружения, никогда не поставляйте auth.json в образе

Для контейнерных и CI-настроек в частности: ничего не монтируйте из ~/.codex, задавайте переменную в окружении контейнера и используйте блок кастомного провайдера с env_key. Эта комбинация не имеет состояния обновления и файла, который может устареть.

Частые паттерны сбоев, с тем, что каждый на самом деле печатает

Всё вышесказанное, в одной сетке. Воспроизведено на Codex CLI 0.146.0 2026-07-30, macOS, против api.openai.com для строк с провайдером по умолчанию и api.ofox.ai для строк с кастомным провайдером.

#НастройкаРезультатВывод
1Нигде нет учётных данных401Missing bearer or basic authentication in header
2Экспортирован OPENAI_API_KEY, провайдер по умолчанию401Missing bearer or basic authentication in header (идентично #1)
3Недействительный ключ записан через --with-api-key401Incorrect API key provided: sk-proj-****7890, auth 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)
12Фиктивный ключ в auth.json, действительный ключ в env_key200env_key имеет приоритет
13ChatGPT auth.json, устаревший токен обновленияошибка обновленияYour access token could not be refreshed...
14base_url без /v1404404 page not found
15wire_api = "chat"ошибка конфигурацииwire_api = "chat" is no longer supported

Строки 9 и 12, это две, которые экономят время, исключая варианты. Если вы охотились за пробелами или считали, что нужно выйти перед настройкой шлюза, оба этих направления, это тупики.

Когда учётные данные починить нельзя: альтернативы, которые работают сейчас

Если препятствие, это сам путь аутентификации, а не опечатка, у вас есть несколько вариантов.

ВариантЧто он решаетКомпромисс
API-ключ вместо входа через ChatGPTУбирает токены обновления, потоки браузера и 8-дневное окно обновленияОплата по токенам вместо покрытия подпиской
Провайдер-шлюз с env_keyОдин ключ, одна переменная, работает в контейнерах и CI без шага входаТребует Responses-совместимый эндпоинт, так что поддержка по моделям варьируется
Второй блок провайдера как ручной запаснойПозволяет переключаться через --config model_provider=..., когда одни учётные данные отказываютВручную, не автоматически
Другой агентOpenCode читает учётные данные провайдера прямо из переменных окружения без шага входаДругой инструмент, другие настройки по умолчанию, см. сравнение лицом к лицу

Маршрут со шлюзом, это тот, что убирает больше всего движущихся частей для автоматизированных настроек, потому что нет шага входа, который может истечь. Ofox работает как один из них: он выставляет /v1/responses, так что требование wire_api = "responses" удовлетворено, и один OFOX_API_KEY достаёт до моделей нескольких вендоров, что означает, что проблема с учётными данными для одного апстрима не оставит вас вообще без чего запустить. Поддержка по модели, а не по шлюзу, так что проверьте нужную модель, прежде чем на неё завязываться. Блок настройки, это тот, что показан в Причине 3, а более длинная версия с ID моделей, в руководстве по настройке API Codex CLI.

Если вы приходите из Claude Code и сравниваете эргономику аутентификации между ними, миграция с Claude Code на Codex охватывает, что переносится, а что придётся перенастроить.

Как не допустить повторения

Несколько привычек, которые предотвращают большинство повторных инцидентов:

Проверяйте настоящим запросом, а не вызовом каталога. Поместите это в свой скрипт настройки вместо пинга /v1/models:

code=$(curl -s -o /dev/null -w "%{http_code}" -X POST https://api.ofox.io/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 в образе и не синхронизируйте его между машинами. Одноразовые токены обновления делают это гонкой по замыслу. Вместо этого внедряйте ключ через окружение.

Запускайте codex doctor после любого изменения конфигурации. Он ловит случаи неправильного CODEX_HOME и неразобранной конфигурации примерно за секунду, а это два сбоя, которые с наибольшей вероятностью заставят вас усомниться в совершенно хорошем ключе.

Источники