From 2b8c6a970a1f9708ce9bf13b84e8c0949e9ed2ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=93=88=E5=9F=BA=E7=B1=B3?= <140241684+BlueX888@users.noreply.github.com> Date: Sun, 20 Sep 2026 07:43:06 +0800 Subject: [PATCH] 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. --- .../deerflow/models/credential_loader.py | 3 ++- backend/tests/test_credential_loader.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/backend/packages/harness/deerflow/models/credential_loader.py b/backend/packages/harness/deerflow/models/credential_loader.py index dc94335aa..9e16c43c3 100644 --- a/backend/packages/harness/deerflow/models/credential_loader.py +++ b/backend/packages/harness/deerflow/models/credential_loader.py @@ -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): diff --git a/backend/tests/test_credential_loader.py b/backend/tests/test_credential_loader.py index 2fc283caa..b9a1b4580 100644 --- a/backend/tests/test_credential_loader.py +++ b/backend/tests/test_credential_loader.py @@ -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")