Skip to content

[codex] add Codex OAuth support - #405

Merged
delete-cloud merged 2 commits into
mainfrom
codex/codex-oauth
May 31, 2026
Merged

delete-cloud merged 2 commits into
mainfrom
codex/codex-oauth

Conversation

@delete-cloud

@delete-cloud delete-cloud commented May 31, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • add a Codex OAuth credential store, device-code client, bearer auth, and CLI commands
  • wire --provider codex through the LLM provider plugin using OAuth-backed OpenAI-compatible transport
  • document the auth/persistence decision in ADR-0050
  • add focused tests for token store permissions, refresh behavior, CLI redaction, and provider wiring

Validation

  • uv run ruff check src/coding_agent/oauth src/coding_agent/cli/oauth_commands.py src/coding_agent/plugins/llm_provider.py src/coding_agent/providers/openai_compat.py tests/coding_agent/test_codex_oauth.py tests/cli/test_oauth_commands.py
  • uv run pytest tests/coding_agent/test_codex_oauth.py tests/cli/test_oauth_commands.py tests/coding_agent/plugins/test_llm_provider.py -k "oauth or codex" -v
  • uv run pytest tests/coding_agent/plugins/test_llm_provider.py tests/coding_agent/test_codex_oauth.py -v
  • uv run pytest tests/cli/ -v
  • uv run python -m coding_agent --help

Notes

Initial implementation was delegated to the o6n coding-agent pod with deepseek-v4-pro; final fixes, tests, and verification were completed locally after the pod stopped before test completion.

Summary by CodeRabbit

  • New Features
    • New coding-agent oauth command group for managing provider authentication with login, status, refresh, logout, and health check commands.
    • Added "codex" as a supported provider with device-code authentication flow.
    • Enhanced LLM provider integration to support OAuth-authenticated requests with automatic token refresh.

@coderabbitai

coderabbitai Bot commented May 31, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@delete-cloud, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 49 minutes and 31 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 11da4a03-d4ae-4a49-b021-bdfdf48fd8a3

📥 Commits

Reviewing files that changed from the base of the PR and between 263ee52 and 13752b4.

📒 Files selected for processing (6)
  • coding-agent/docs/adr/0050-codex-oauth-provider.md
  • coding-agent/src/coding_agent/oauth/auth.py
  • coding-agent/src/coding_agent/oauth/cli.py
  • coding-agent/src/coding_agent/oauth/store.py
  • coding-agent/tests/cli/test_oauth_commands.py
  • coding-agent/tests/coding_agent/test_codex_oauth.py
📝 Walkthrough

Walkthrough

This PR adds OAuth 2.0 device-code authentication for OpenAI Codex API. It introduces credential storage with cross-platform file locking, a Codex OAuth client, HTTP bearer token injection with automatic refresh, CLI commands for managing credentials, and integration with the LLM provider system.

Changes

Codex OAuth Implementation

Layer / File(s) Summary
OAuth Data Models and Contracts
src/coding_agent/oauth/types.py, src/coding_agent/oauth/__init__.py
Core Pydantic models define OAuthTokens, OAuthAccount, OAuthProviderRecord (with with_refreshed_tokens() method), AuthFile (versioned schema), TokenSnapshot (request-safe representation), and OAuthTokenSource protocol with async get_token() and refresh_token(). Package __init__.py re-exports public API.
File-Backed OAuth Store with Cross-Platform Locking
src/coding_agent/oauth/store.py
FileLock context manager provides advisory locking (Windows msvcrt / POSIX fcntl), OAuthStore manages locked file I/O with atomic writes (temp file + os.replace), permission enforcement (0o700 directory / 0o600 file), and provider-level CRUD. StoreBackedTokenSource bridges store credentials to token refresh via asyncio.to_thread. Tests verify strict permissions and refresh/persistence behavior.
Codex OAuth Client and Device-Code Flow
src/coding_agent/oauth/codex.py
CodexOAuthProfile holds Codex endpoints and scopes. DeviceCode models device-authorization response. CodexOAuthClient orchestrates device-code request → polling with timeout → token exchange, refreshes credentials with account ID validation, and revokes tokens. JWT parsing (without signature verification) extracts OAuthAccount metadata from ID tokens. redact_record() sanitizes stored tokens for display.
Store-Backed Token Source and HTTP Bearer Auth
src/coding_agent/oauth/auth.py, src/coding_agent/oauth/store.py (StoreBackedTokenSource)
OAuthBearerAuth implements httpx.Auth with sync/async flows: injects Authorization: Bearer <token>, optionally adds extra headers, and on 401 response refreshes token and retries. StoreBackedTokenSource wraps store access and delegated refresh, raising RuntimeError for missing login. Tests verify 401 retry behavior and token redaction.
OAuth CLI Commands and Wiring
src/coding_agent/oauth/cli.py, src/coding_agent/cli/oauth_commands.py, src/coding_agent/cli/main.py, src/coding_agent/core/config.py
oauth_cli Click group defines login (device-code flow), status (identity + refresh timestamp), refresh (token refresh), logout (with optional token revocation), and doctor (permission diagnostics and redacted token display). oauth_commands.py shim re-exports for CLI integration. main.py registers command, config.py adds "codex" to ProviderName. CLI tests validate missing-provider messaging and token redaction.
LLM Provider Integration with OAuth Backend
src/coding_agent/plugins/llm_provider.py, src/coding_agent/providers/openai_compat.py
LLMProviderPlugin.provide_llm adds "codex" branch: loads stored OAuth record, raises if missing, constructs OAuthBearerAuth token source, derives base URL, and creates OpenAICompatProvider with auth handler. StoreBackedCodexTokenSource wraps token source with CodexOAuthClient refresh delegation. openai_compat.py accepts optional httpx_auth parameter. Tests verify missing-login error and provider creation with correct model/URL.
Architecture Decision Record
docs/adr/0050-codex-oauth-provider.md
Documents device-flow design, proposed src/coding_agent/oauth/ module architecture, CLI commands, --provider codex selection, acceptance criteria, risks, and alternatives.

Sequence Diagram

sequenceDiagram
  participant User
  participant CLI as oauth_cli
  participant Client as CodexOAuthClient
  participant Endpoint as OAuth Endpoint
  participant Store as OAuthStore
  participant LLMPlugin as LLMProviderPlugin

  User->>CLI: oauth login codex
  CLI->>Client: request_device_code()
  Client->>Endpoint: POST /device_code
  Endpoint-->>Client: {user_code, device_auth_id}
  CLI-->>User: Visit URL + enter code
  User->>Endpoint: Authorize device
  CLI->>Client: poll_device_token(timeout=15min)
  Client->>Endpoint: Poll /device_token
  Endpoint-->>Client: {access_token, refresh_token, id_token}
  Client->>Store: set_provider("codex", record)
  Store-->>CLI: Success
  CLI-->>User: Logged in as user@example.com

  User->>LLMPlugin: create LLM with "codex" provider
  LLMPlugin->>Store: get_provider("codex")
  Store-->>LLMPlugin: OAuthProviderRecord
  LLMPlugin->>LLMPlugin: create OAuthBearerAuth(token_source)
  LLMPlugin->>LLMPlugin: create OpenAICompatProvider(auth=bearer_auth)
  LLMPlugin-->>User: Provider ready
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 A device dances, codes in hand so bright,
OAuth flows like springtime through the night,
Tokens cached in files with secrets sealed tight,
Bearer auth refreshes 401 blight—
Codex awakens, credentials alight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[codex] add Codex OAuth support' accurately summarizes the main change: adding OAuth support for the Codex provider with implementation of credential store, device-code client, bearer auth, CLI commands, and LLM provider integration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/codex-oauth

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@delete-cloud
delete-cloud marked this pull request as ready for review May 31, 2026 05:03
@delete-cloud

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
coding-agent/tests/cli/test_oauth_commands.py (1)

45-59: ⚡ Quick win

Add a fresh-install oauth doctor test.

This only covers the happy path where the auth directory already exists. A case with --auth-file under a nonexistent subdirectory would catch the missing-path regression in doctor().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@coding-agent/tests/cli/test_oauth_commands.py` around lines 45 - 59, Add a
new test that exercises the "fresh-install" path by invoking the CLI's "oauth
doctor" command with an --auth-file path whose parent directory does not exist
(so the code must create the directory); reuse the same assertions as
test_oauth_doctor_redacts_tokens (check exit_code == 0, that provider text and
"<redacted>" appear, and that secret-access-token/secret-refresh-token do not
appear) to ensure doctor() handles creating the missing directory and still
redacts tokens when run via main with ["oauth", "doctor", "--auth-file",
str(auth_file)].
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@coding-agent/docs/adr/0050-codex-oauth-provider.md`:
- Around line 104-105: Replace the machine-local absolute paths
`/workspace/ya-oauth-ref/packages/ya-oauth/` and
`/workspace/ya-oauth-ref/packages/ya-oauth-provider/` with portable references:
use repository-relative paths such as `./packages/ya-oauth/` and
`./packages/ya-oauth-provider/` or, if linking external docs, replace them with
full repo URLs (e.g., the GitHub tree links) so other contributors can resolve
the references.

In `@coding-agent/src/coding_agent/oauth/auth.py`:
- Line 22: The module sets requires_response_body = True which forces httpx to
buffer every response and breaks streaming; remove or set requires_response_body
to False (or delete that attribute) in coding_agent/oauth/auth.py and instead
explicitly read the response body only in the 401 retry path by calling
response.read() (sync) or await response.aread() (async) inside the code that
handles the 401/retry logic (where you currently examine the response to decide
to refresh credentials and retry). This keeps streaming responses incremental
while still consuming the body when necessary for the 401 handling.

In `@coding-agent/src/coding_agent/oauth/cli.py`:
- Around line 168-171: The _print_mode function currently calls os.stat(path)
which raises FileNotFoundError for missing files/dirs; wrap the stat call in a
try/except (or check path.exists()) inside _print_mode so missing paths are
handled gracefully — when the path does not exist, print a clear diagnostic like
"<label> missing" (or "not found") instead of raising, and keep the existing
behavior for existing paths (compute mode with stat.S_IMODE and compare to
expected). Update references to _print_mode (used by the oauth doctor command)
so doctor continues and prints other checks even if this path is absent.

In `@coding-agent/src/coding_agent/oauth/store.py`:
- Around line 202-212: The update callback passed to OAuthStore.update currently
calls self._refresh_provider(record) while holding the auth-file lock, blocking
other readers/writers; instead, read the existing record inside the update
callback (using auth_file.providers.get(self.provider_name)) and return
immediately without refreshing, then release the lock and call
self._refresh_provider(record) outside the lock; after refresh, reacquire the
lock with a second call to self.store.update to re-read the current stored
record, verify it hasn't been changed (or handle conflict if it has), and then
persist the refreshed record back into auth_file.providers[self.provider_name];
reference OAuthStore.update, the update(inner) callback, self._refresh_provider,
auth_file.providers and self.provider_name when making the changes.

In `@coding-agent/tests/coding_agent/test_codex_oauth.py`:
- Around line 42-43: The POSIX-only permission assertions in the test (the two
asserts using stat.S_IMODE on auth_path.parent.stat().st_mode and
auth_path.stat().st_mode) should be skipped on Windows: wrap or guard these
checks with a platform check (e.g., if os.name != "nt") so they only run on
POSIX systems; import os in the test if needed and place the two assertions
inside that conditional block to avoid false failures on Windows.

---

Nitpick comments:
In `@coding-agent/tests/cli/test_oauth_commands.py`:
- Around line 45-59: Add a new test that exercises the "fresh-install" path by
invoking the CLI's "oauth doctor" command with an --auth-file path whose parent
directory does not exist (so the code must create the directory); reuse the same
assertions as test_oauth_doctor_redacts_tokens (check exit_code == 0, that
provider text and "<redacted>" appear, and that
secret-access-token/secret-refresh-token do not appear) to ensure doctor()
handles creating the missing directory and still redacts tokens when run via
main with ["oauth", "doctor", "--auth-file", str(auth_file)].
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d4d3b7e-f932-4a3d-ac80-07158285783f

📥 Commits

Reviewing files that changed from the base of the PR and between 066b556 and 263ee52.

📒 Files selected for processing (15)
  • coding-agent/docs/adr/0050-codex-oauth-provider.md
  • coding-agent/src/coding_agent/cli/main.py
  • coding-agent/src/coding_agent/cli/oauth_commands.py
  • coding-agent/src/coding_agent/core/config.py
  • coding-agent/src/coding_agent/oauth/__init__.py
  • coding-agent/src/coding_agent/oauth/auth.py
  • coding-agent/src/coding_agent/oauth/cli.py
  • coding-agent/src/coding_agent/oauth/codex.py
  • coding-agent/src/coding_agent/oauth/store.py
  • coding-agent/src/coding_agent/oauth/types.py
  • coding-agent/src/coding_agent/plugins/llm_provider.py
  • coding-agent/src/coding_agent/providers/openai_compat.py
  • coding-agent/tests/cli/test_oauth_commands.py
  • coding-agent/tests/coding_agent/plugins/test_llm_provider.py
  • coding-agent/tests/coding_agent/test_codex_oauth.py

Comment thread coding-agent/docs/adr/0050-codex-oauth-provider.md Outdated
Comment thread coding-agent/src/coding_agent/oauth/auth.py Outdated
Comment thread coding-agent/src/coding_agent/oauth/cli.py
Comment thread coding-agent/src/coding_agent/oauth/store.py Outdated
Comment thread coding-agent/tests/coding_agent/test_codex_oauth.py Outdated
@delete-cloud
delete-cloud merged commit acc8d52 into main May 31, 2026
1 check passed
@delete-cloud
delete-cloud deleted the codex/codex-oauth branch May 31, 2026 05:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant