fix(codex): accept codex login as authentication (#1010) - #1023
fix(codex): accept codex login as authentication (#1010)#1023frankbria wants to merge 3 commits into
codex login as authentication (#1010)#1023Conversation
`cf work start --engine codex --execute` and `cf work batch run --engine codex` called require_openai_api_key(), which exits when OPENAI_API_KEY is unset. But codex does not need that variable — `codex login` stores ChatGPT-plan credentials in auth.json, and the #914 end-to-end demo drove a real codex app-server to completion with no OPENAI_API_KEY at all. So the advertised `--engine codex` was unusable for the common case, even though the adapter and the binary both worked. The env var is not merely an incomplete proof of auth. A real logged-in auth.json on this machine reads: auth_mode = 'chatgpt' OPENAI_API_KEY = None # literally null tokens = {access_token, account_id, id_token, refresh_token} CodexAdapter.is_authenticated() now accepts either route: the env var, an API key stored in auth.json (`codex login --api-key`), or ChatGPT tokens. Presence of the file is not enough — `codex logout` can leave it behind with empty tokens. $CODEX_HOME is honoured, since the CLI documents it. requirements() no longer lists OPENAI_API_KEY. `cf engines check` treats any unsatisfied entry as an unmet requirement and exits 1, so listing it there reported a working logged-in codex as broken — the same bug at a second surface. check_ready() reports codex_binary + authenticated instead, which is the question actually being asked. Verified against the real logged-in codex with no OPENAI_API_KEY in the environment: is_authenticated() True, check_requirements('codex') all True; and False for both when CODEX_HOME points somewhere empty.
|
Claude finished @frankbria's task in 4m 17s —— View job GLM Precision Review — complete ✅
Result: 1 minor finding — |
|
|
||
| try: | ||
| auth = json.loads((cls.codex_home() / "auth.json").read_text()) | ||
| except (OSError, json.JSONDecodeError): |
There was a problem hiding this comment.
[minor] Path.read_text() can raise UnicodeDecodeError (a ValueError), which except (OSError, json.JSONDecodeError) does not catch — a genuinely corrupt auth.json crashes is_authenticated() instead of returning False.
Failure scenario: auth.json is truncated or written with non-UTF-8 bytes (e.g. a partial write interrupted mid-codex login, or an editor that re-saved in a legacy encoding). read_text() defaults to strict decoding and raises UnicodeDecodeError, which is neither an OSError nor a JSONDecodeError, so it propagates uncaught through require_codex_auth() — the user gets a Python traceback at cf work start --engine codex --execute instead of the intended "codex is not authenticated" message. The existing corrupt-file test only exercises invalid-JSON-over-valid-UTF-8 ("{not json"), which is why this path slipped through. JSONDecodeError is itself a ValueError, so broadening to ValueError covers both.
| except (OSError, json.JSONDecodeError): | |
| except (OSError, ValueError): |
GLM precision review
1 finding (1 minor). No critical/major defects. Verified correct: |
…1010) CI caught this: `all(reqs.values())` fails on any runner without the codex CLI installed, because check_ready reports codex_binary: False there. That is correct behaviour, and unrelated to what the test is about. AC3's claim is narrower — a logged-in codex must not be flagged for a *missing key* — so the test now asserts exactly that: authenticated is True and no OPENAI_API_KEY entry appears in the requirements at all. The negative test's `not all(...)` had the mirror problem: it passed trivially wherever the binary was absent, so it would not have caught a regression.
|
Claude finished @frankbria's task in 4m 2s —— View job GLM Precision Review — re-review on synchronizeNew commit
✅ GLM review: no new defects in this synchronizeVerified the test change is correct and non-vacuous:
Note: the one minor finding from the prior review — |
read_text() raises UnicodeDecodeError on a non-UTF-8 file. That is a ValueError, not an OSError, so `except (OSError, JSONDecodeError)` did not catch it and a genuinely corrupt auth.json crashed is_authenticated() instead of reading as 'not authenticated'. Widened to ValueError, which subsumes JSONDecodeError too. Two tests: a non-UTF-8 file, and a directory where the file should be (the OSError half).
|
Claude finished @frankbria's task in 2m 12s —— View job GLM Precision Review — re-review on synchronize ✅New commit
✅ GLM review: no new defects in this synchronizeThe fix is correct and non-vacuously tested:
The prior |
Closes #1010.
The bug
cf work start --engine codex --executeandcf work batch run --engine codexcalledrequire_openai_api_key(), which exits whenOPENAI_API_KEYis unset. codex does not need that variable —codex loginstores ChatGPT-plan credentials inauth.json, and #914's end-to-end demo drove a realcodex app-serverto completion with none set.So the advertised
--engine codexwas unusable for the common case, while the adapter and the binary both worked fine.The env var is not just incomplete proof — the file says so
A real logged-in
auth.jsonon this machine:codex loginrecordsOPENAI_API_KEY: nullin the very file that proves you are authenticated.Fix
CodexAdapter.is_authenticated()accepts either route — the env var, an API key stored inauth.json(codex login --api-key), or ChatGPT tokens. Presence of the file is not enough:codex logoutcan leave it behind with empty tokens.$CODEX_HOMEis honoured, since the CLI documents it.require_codex_auth()replacesrequire_openai_api_key()at both call sites, and its message names both ways in.A second surface, found by running the command
AC3 asks that
cf engines checkreport codex ready when logged in. It did not, for a different reason: the command treats any unsatisfied entry as an unmet requirement and exits 1, sorequirements()listingOPENAI_API_KEYreported a working logged-in codex as broken.requirements()now returns{}andcheck_ready()reportscodex_binary+authenticated— the question actually being asked. (credential_env_vars()is overridden explicitly on this adapter, so it is unaffected.)Verification against the real logged-in codex
With no
OPENAI_API_KEYin the environment:And all three AC4 cases:
Testing
tests/core/adapters/test_codex_auth_1010.py— 14 tests covering logged-in-no-env-key, env-key-no-login, key-stored-in-auth.json, neither, logged-out-with-empty-tokens, corrupt file,$CODEX_HOMErelocation, and bothengines checkverdicts.ruffclean.One test pins the unauthenticated case against
load_env_files(), since this machine (and CI) may carry a realOPENAI_API_KEYin a.env.Note
This composes with #996:
~/.codexis symlinked into the delegated agent's sandbox home, so the login keeps working for the spawnedcodex app-servertoo.