Skip to content

fix(acp): require tokens key before selecting chatgpt auth method#3628

Merged
simonrosenberg merged 1 commit into
mainfrom
openhands/fix-acp-auth-format-check-3627
Jun 17, 2026
Merged

fix(acp): require tokens key before selecting chatgpt auth method#3628
simonrosenberg merged 1 commit into
mainfrom
openhands/fix-acp-auth-format-check-3627

Conversation

@simonrosenberg

@simonrosenberg simonrosenberg commented Jun 10, 2026

Copy link
Copy Markdown
Member

HUMAN:

When acp_isolate_data_dir=True and codex-acp is driven with API-key auth
(OPENAI_API_KEY only, no CODEX_AUTH_JSON), the first session/new
succeeds, and during that session codex rewrites $CODEX_HOME/auth.json
with {"auth_mode": "apikey", "OPENAI_API_KEY": "..."}.

On the next launch (pod recycle / agent-server restart) _select_auth_method
in acp_agent.py saw the file at _codex_auth_file(env) and returned
"chatgpt", because the check only verified file presence:

if "chatgpt" in method_ids and _codex_auth_file(env).is_file():
return "chatgpt"

  • A human has tested these changes.

AGENT:


Why

When acp_isolate_data_dir=True and codex-acp is driven with API-key auth
(OPENAI_API_KEY only, no CODEX_AUTH_JSON), the first session/new
succeeds, and during that session codex rewrites $CODEX_HOME/auth.json
with {"auth_mode": "apikey", "OPENAI_API_KEY": "..."}.

On the next launch (pod recycle / agent-server restart) _select_auth_method
in acp_agent.py saw the file at _codex_auth_file(env) and returned
"chatgpt", because the check only verified file presence:

if "chatgpt" in method_ids and _codex_auth_file(env).is_file():
    return "chatgpt"

codex-acp then read the apikey-format file as if it were a ChatGPT token
blob and hung indefinitely waiting for browser-based OAuth.

The production cloud path is unaffected — canvas always sends
CODEX_AUTH_JSON so the SDK materialises a proper chatgpt-format
auth.json before codex starts and codex never overwrites it. The bug
only manifested in test harnesses / non-standard deployments running with
API-key auth and acp_isolate_data_dir=True (discovered during the ACP
cloud pivot local validation, canvas#1290).

Summary

  • Add _codex_auth_file_is_chatgpt(env) which parses auth.json and
    requires a top-level "tokens" key — the marker that distinguishes
    the ChatGPT subscription token blob from the apikey-mode file codex
    writes for itself.
  • Gate the chatgpt branch in _select_auth_method on this stricter
    check so an apikey-format file falls through to the
    openai-api-key / codex-api-key fallback instead of triggering
    the OAuth hang.
  • Update existing fixtures that wrote {} as auth.json to use the
    real chatgpt-format payload, and add two new tests:
    test_apikey_format_auth_file_falls_back_to_api_key (reproduces the
    bug from fix(acp): _select_auth_method picks chatgpt when $CODEX_HOME/auth.json is in apikey format, causing auth hang on resume #3627) and test_malformed_auth_file_falls_back_to_api_key
    (defensive coverage for unreadable / non-JSON files).

Issue Number

Fixes #3627

How to Test

uv run pytest tests/sdk/agent/test_acp_agent.py::TestSelectAuthMethod -x -q

Result: 20 passed, including the two new tests
(test_apikey_format_auth_file_falls_back_to_api_key,
test_malformed_auth_file_falls_back_to_api_key) that fail on main
and pass with this change.

End-to-end repro (matches the issue's reproduction sequence):

  1. Create an ACPAgent conversation with acp_isolate_data_dir=True,
    OPENAI_API_KEY in secrets (no CODEX_AUTH_JSON).
  2. First turn completes via openai-api-key. codex-acp writes
    $CODEX_HOME/auth.json with {"auth_mode": "apikey", ...}.
  3. Restart the agent-server.
  4. Send a follow-up message. With this fix, _select_auth_method
    returns "openai-api-key" (file does not contain "tokens")
    instead of "chatgpt", and the conversation proceeds without
    hanging on browser-based OAuth.

Video/Screenshots

N/A — auth-path selection change, exercised through the new unit tests.

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

  • Behavior is unchanged on the production cloud path (CODEX_AUTH_JSON
    materialises a proper chatgpt-format file with "tokens").
  • The helper accepts only dicts containing "tokens"; malformed or
    non-dict JSON also falls back to the API-key path, which is the safe
    default.

This PR was created by an AI agent (OpenHands) on behalf of the user.

@simonrosenberg can click here to continue refining the PR


Agent Server images for this PR

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:4e17202-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-4e17202-python \
  ghcr.io/openhands/agent-server:4e17202-python

All tags pushed for this build

ghcr.io/openhands/agent-server:4e17202-golang-amd64
ghcr.io/openhands/agent-server:4e17202a131eac18a31c5ad4e89feb73d49c5574-golang-amd64
ghcr.io/openhands/agent-server:openhands-fix-acp-auth-format-check-3627-golang-amd64
ghcr.io/openhands/agent-server:4e17202-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:4e17202-golang-arm64
ghcr.io/openhands/agent-server:4e17202a131eac18a31c5ad4e89feb73d49c5574-golang-arm64
ghcr.io/openhands/agent-server:openhands-fix-acp-auth-format-check-3627-golang-arm64
ghcr.io/openhands/agent-server:4e17202-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:4e17202-java-amd64
ghcr.io/openhands/agent-server:4e17202a131eac18a31c5ad4e89feb73d49c5574-java-amd64
ghcr.io/openhands/agent-server:openhands-fix-acp-auth-format-check-3627-java-amd64
ghcr.io/openhands/agent-server:4e17202-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:4e17202-java-arm64
ghcr.io/openhands/agent-server:4e17202a131eac18a31c5ad4e89feb73d49c5574-java-arm64
ghcr.io/openhands/agent-server:openhands-fix-acp-auth-format-check-3627-java-arm64
ghcr.io/openhands/agent-server:4e17202-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:4e17202-python-amd64
ghcr.io/openhands/agent-server:4e17202a131eac18a31c5ad4e89feb73d49c5574-python-amd64
ghcr.io/openhands/agent-server:openhands-fix-acp-auth-format-check-3627-python-amd64
ghcr.io/openhands/agent-server:4e17202-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:4e17202-python-arm64
ghcr.io/openhands/agent-server:4e17202a131eac18a31c5ad4e89feb73d49c5574-python-arm64
ghcr.io/openhands/agent-server:openhands-fix-acp-auth-format-check-3627-python-arm64
ghcr.io/openhands/agent-server:4e17202-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:4e17202-golang
ghcr.io/openhands/agent-server:4e17202a131eac18a31c5ad4e89feb73d49c5574-golang
ghcr.io/openhands/agent-server:openhands-fix-acp-auth-format-check-3627-golang
ghcr.io/openhands/agent-server:4e17202-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:4e17202-java
ghcr.io/openhands/agent-server:4e17202a131eac18a31c5ad4e89feb73d49c5574-java
ghcr.io/openhands/agent-server:openhands-fix-acp-auth-format-check-3627-java
ghcr.io/openhands/agent-server:4e17202-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:4e17202-python
ghcr.io/openhands/agent-server:4e17202a131eac18a31c5ad4e89feb73d49c5574-python
ghcr.io/openhands/agent-server:openhands-fix-acp-auth-format-check-3627-python
ghcr.io/openhands/agent-server:4e17202-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim

About Multi-Architecture Support

  • Each variant tag (e.g., 4e17202-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., 4e17202-python-amd64) are also available if needed

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-sdk/openhands/sdk/agent
   acp_agent.py117610291%450, 815–817, 1054–1055, 1098, 1100, 1104, 1108, 1134, 1197–1198, 1203, 1270, 1598, 1601–1602, 1619–1620, 1656, 1661, 1769, 1774, 2108–2109, 2394–2397, 2401–2403, 2406–2410, 2412, 2637, 2651–2652, 2655–2657, 2665, 2669, 2673–2674, 2680–2681, 2693–2694, 2697, 2731, 2735–2737, 2741–2742, 2774, 2858, 3045–3047, 3050–3051, 3091, 3233, 3237, 3245–3247, 3285–3286, 3289, 3297–3299, 3301, 3303, 3307, 3310, 3319–3321, 3323, 3359–3360, 3378–3381, 3384, 3388–3390, 3392, 3396–3397, 3612–3613
TOTAL31506857772% 

When codex-acp runs in apikey mode it rewrites $CODEX_HOME/auth.json with
{"auth_mode": "apikey", "OPENAI_API_KEY": "..."}. On the next launch
_select_auth_method saw the file and returned 'chatgpt' — but the payload
is in apikey format, so codex hung forever waiting for browser-based OAuth.

Gate chatgpt selection on the presence of the 'tokens' key (the ChatGPT
subscription token blob) rather than mere file existence.

Co-authored-by: openhands <openhands@all-hands.dev>
@simonrosenberg simonrosenberg force-pushed the openhands/fix-acp-auth-format-check-3627 branch from 42e8d04 to 4e17202 Compare June 17, 2026 18:54
@simonrosenberg simonrosenberg marked this pull request as ready for review June 17, 2026 19:07
@simonrosenberg simonrosenberg self-assigned this Jun 17, 2026
@simonrosenberg simonrosenberg added the acp About ACP label Jun 17, 2026

all-hands-bot commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Review complete.

This review was performed through OpenHands Cloud Automation. You can log in and view the conversation here.

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: fix(acp): require tokens key before selecting chatgpt auth method

Taste Rating: 🟢 Good taste

Clean, well-scoped fix that solves a real bug without over-engineering.


Summary

This PR fixes a bug (issue #3627) where codex-acp would incorrectly select chatgpt auth method after detecting the presence of auth.json, even when the file was written in apikey format by codex itself. This caused the agent to hang indefinitely waiting for browser-based OAuth.

Analysis

[openhands-sdk/openhands/sdk/agent/acp_agent.py]

The change is minimal and focused:

  1. _codex_auth_file_is_chatgpt() (lines 287-308) - New helper that validates the auth file contains the ChatGPT subscription marker (tokens key) before claiming it's usable for chatgpt auth. The docstring clearly explains the root cause and why tokens is the distinguishing key.

  2. _select_auth_method() (line 332) - One-line change to use the stricter validation instead of file presence check.

[tests/sdk/agent/test_acp_agent.py]

  1. Added _CHATGPT_AUTH_JSON constant with proper fixture data containing tokens key
  2. Updated three existing tests to use the proper chatgpt-format payload instead of {}
  3. Added two regression tests:
    • test_apikey_format_auth_file_falls_back_to_api_key - reproduces the exact bug
    • test_malformed_auth_file_falls_back_to_api_key - defensive coverage for unreadable/non-JSON files

Verification

  • The fix targets the exact failure mode described in the issue
  • The existing tests that were silently passing with {} are now fixed to use proper fixtures
  • Two new tests explicitly cover the bug scenario and defensive edge cases
  • The production cloud path is unaffected (canvas always sends CODEX_AUTH_JSON with proper format)

[RISK ASSESSMENT]

  • Overall PR ⚠️ Risk Assessment: 🟢 LOW
  • The change is contained to a single file pair (implementation + tests)
  • No API changes or breaking changes
  • Tests are real behavioral tests, not just mock assertions
  • Well-documented with clear references to the original issue

VERDICT:Worth merging

KEY INSIGHT: The original bug had a subtle race condition where a valid file format (apikey) was being misidentified due to only checking file presence. The fix correctly validates the actual content marker (tokens key) rather than just existence.


This review was generated by an AI agent (OpenHands) on behalf of the user through OpenHands Automation. View conversation

@simonrosenberg simonrosenberg merged commit 12751ff into main Jun 17, 2026
58 of 64 checks passed
@simonrosenberg simonrosenberg deleted the openhands/fix-acp-auth-format-check-3627 branch June 17, 2026 19:12

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ QA Report: PASS

The ACP auth selector now falls back to API-key auth for Codex-written apikey/malformed auth.json files while preserving ChatGPT-token selection.

Does this PR achieve its stated goal?

Yes. I reproduced the PR's stated failure mode against the exact PR base commit (cabe776b): with CODEX_HOME/auth.json containing Codex's apikey rewrite, the SDK selected chatgpt. Re-running the same SDK call at PR head (4e17202a) selected openai-api-key for apikey-format and malformed files, while a real ChatGPT token blob still selected chatgpt.

Phase Result
Environment Setup make build completed and installed the uv environment.
CI Status ⚠️ PR is merged; observed checks: 35 success, 1 failure (PR Description Check), 2 in progress, 17 skipped.
Functional Verification ✅ Before/after SDK execution confirms the auth-method selection behavior changed as intended.
Functional Verification

Test 1: Codex apikey rewrite should not force ChatGPT auth after restart

The QA script imported the SDK auth selector, advertised chatgpt and openai-api-key auth methods, then created temporary CODEX_HOME/auth.json files for three realistic cases: Codex's apikey rewrite, malformed JSON, and a ChatGPT token blob.

Step 1 — Reproduce baseline without the fix:
Ran git checkout cabe776b404a729770b903923c8c2b5d935d2597 && OPENHANDS_SUPPRESS_BANNER=1 uv run python /tmp/acp_auth_qa.py:

commit: cabe776b
codex apikey rewrite: chatgpt
malformed auth file: chatgpt
chatgpt token blob: chatgpt
baseline exit: 0

This confirms the bug on the PR base: any present auth.json, including Codex's apikey-format file, caused chatgpt selection.

Step 2 — Apply the PR's changes:
Checked out PR head 4e17202a131eac18a31c5ad4e89feb73d49c5574.

Step 3 — Re-run with the fix in place:
Ran git checkout 4e17202a131eac18a31c5ad4e89feb73d49c5574 && OPENHANDS_SUPPRESS_BANNER=1 uv run python /tmp/acp_auth_qa.py:

commit: 4e17202a
codex apikey rewrite: openai-api-key
malformed auth file: openai-api-key
chatgpt token blob: chatgpt
pr exit: 0

This shows the fix works for the reported restart scenario: apikey-format and malformed files no longer trigger ChatGPT OAuth, and the intended ChatGPT-token path still works.

Unable to Verify

I did not run a live codex-acp conversation against real OpenAI/Codex credentials; no such credentials or browser OAuth session were available in this QA environment. The exercised behavior is the SDK auth-selection gate that determines whether codex-acp would enter the problematic ChatGPT OAuth path.

Issues Found

None.

This review was created by an AI agent (OpenHands) on behalf of the user.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

acp About ACP

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(acp): _select_auth_method picks chatgpt when $CODEX_HOME/auth.json is in apikey format, causing auth hang on resume

3 participants