fix(models): degrade a non-object Codex auth file to no credential (#5584)

load_codex_cli_credential called .get on the parsed ~/.codex/auth.json
(and $CODEX_AUTH_PATH) without checking that the top level is an object.
_load_json_file returns any valid JSON value, so an array or scalar payload
raised AttributeError out of CodexChatModel.model_post_init instead of the
documented 'Codex CLI credential not found' error. Guard the top level the
same way the sibling Claude loader and its own nested tokens guard do.
This commit is contained in:
哈基米 2026-09-20 07:43:06 +08:00 committed by GitHub
parent 40bd1fbcbf
commit 2b8c6a970a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 21 additions and 1 deletions

View File

@ -227,7 +227,8 @@ def load_codex_cli_credential() -> CodexCliCredential | None:
"""Load credential from Codex CLI (~/.codex/auth.json)."""
cred_path = _resolve_credential_path("CODEX_AUTH_PATH", ".codex/auth.json")
data = _load_json_file(cred_path, "Codex CLI credentials")
if data is None:
if not isinstance(data, dict):
logger.debug("Codex CLI credentials file is not a JSON object; skipping")
return None
tokens = data.get("tokens", {})
if not isinstance(tokens, dict):

View File

@ -12,6 +12,7 @@ from deerflow.models.credential_loader import (
load_claude_code_credential,
load_codex_cli_credential,
)
from deerflow.models.openai_codex_provider import CodexChatModel
@pytest.fixture(autouse=True)
@ -331,3 +332,21 @@ def test_load_codex_cli_credential_supports_legacy_top_level_shape(tmp_path, mon
assert cred is not None
assert cred.access_token == "legacy-access-token"
assert cred.account_id == ""
@pytest.mark.parametrize("payload", [[], "codex-access-token", 5])
def test_load_codex_cli_credential_ignores_non_object_auth_file(tmp_path, monkeypatch, payload):
auth_path = tmp_path / "auth.json"
auth_path.write_text(json.dumps(payload))
monkeypatch.setenv("CODEX_AUTH_PATH", str(auth_path))
assert load_codex_cli_credential() is None
def test_codex_chat_model_reports_missing_credential_for_non_object_auth_file(tmp_path, monkeypatch):
auth_path = tmp_path / "auth.json"
auth_path.write_text(json.dumps([]))
monkeypatch.setenv("CODEX_AUTH_PATH", str(auth_path))
with pytest.raises(ValueError, match="Codex CLI credential not found"):
CodexChatModel(model="gpt-5.4")