Skip to content

feat(tui): native ChatGPT PKCE sign-in for openai-codex (#5778) - #5784

Merged
Hmbown merged 14 commits into
mainfrom
feat/native-chatgpt-pkce-5778-20260831
Sep 1, 2026
Merged

feat(tui): native ChatGPT PKCE sign-in for openai-codex (#5778)#5784
Hmbown merged 14 commits into
mainfrom
feat/native-chatgpt-pkce-5778-20260831

Conversation

@Hmbown

@Hmbown Hmbown commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Summary

Native Sign in with ChatGPT for the first-class openai-codex route. Connecting a ChatGPT/Codex subscription no longer requires Codex CLI or ~/.codex/auth.json. Browser PKCE with a localhost callback stores refreshable tokens in Codewhale-owned storage, matching the xAI device-login pattern.

Closes #5778.

Terms research (ambiguous — implemented behind a documented boundary)

Public sources checked 2026-08-31:

  • learn.chatgpt.com/docs/auth (OpenAI Codex authentication): Sign in with ChatGPT is documented for the ChatGPT desktop app, Codex CLI, and IDE extension. Browser PKCE with a localhost callback (default localhost:1455) is the official Codex client flow. Device-code is documented as a Codex CLI beta (codex login --device-auth), not as a third-party API.
  • Issuer OIDC discovery (https://auth.openai.com/.well-known/openid-configuration): advertises authorization_code + PKCE S256, refresh_token, scopes openid profile email offline_access, and revocation_endpoint. Does not advertise device_authorization_endpoint.
  • OpenAI has not published a third-party client-registration path for this public Codex OAuth client (app_EMoamEEZ73f0CkXaXp7hrann, Apache-2.0 Codex CLI).
  • OpenAI maintainer comment on openai/codex#8338: ToS/code license described as permissive; OSS projects like OpenCode doing similar login are acknowledged. Not a legal opinion.
  • CI/CD auth docs explicitly exclude “generic OAuth clients outside Codex.”
  • Unlike Anthropic, there is no explicit ban.

Conclusion: ambiguous. This PR implements the published authorization-code + PKCE S256 adapter and does not smuggle unpublished endpoints:

  • honest originator=codewhale (never codex_cli_rs)
  • loopback ports 1455 / 1457 (the ports that public client registers)
  • /oauth/authorize and /oauth/token on the published issuer
  • discovery revocation_endpoint for remote revoke
  • no /api/accounts/deviceauth/* (unpublished; issuer does not advertise device authorization)
  • tokens stay in Codewhale-owned $CODEWHALE_HOME/credentials/chatgpt-auth-<hex>.json; Codex CLI files are never written or refreshed
  • no session cookies

If OpenAI later allocates a Codewhale-specific client id, swap CODEWHALE_CHATGPT_OAUTH_CLIENT_ID.

Behavior

  • /provider setup openai-codex (and missing-auth handoff) offers Sign in with ChatGPT first (subscription / ChatGPT billing) vs Import Codex CLI credentials (explicit read-only consent, Codex CLI remains owner). Copy distinguishes this from the openai API-key billing owner before any run.
  • codewhale auth chatgpt / /auth chatgpt runs PKCE login.
  • codewhale auth chatgpt-revoke / /auth chatgpt-revoke deletes Codewhale-owned tokens (best-effort remote revoke) and does not touch Codex CLI consent.
  • Credential lookup: env token → Codewhale-owned ChatGPT store (refresh under lock, rotating refresh tokens fail closed) → consent-gated Codex CLI file.
  • External CLI import remains an explicit alternative, not a prerequisite.

Testing

  • cargo fmt --all (clean)
  • cargo clippy -p codewhale-config --tests -- -D warnings
  • cargo clippy -p codewhale-tui --tests -- -D warnings -A clippy::too_many_arguments (the allow is pre-existing on runtime_threads / underwater, not introduced here)
  • cargo clippy -p codewhale-cli --tests -- -D warnings -A clippy::too_many_arguments
  • cargo test -p codewhale-config --lib xai_credentials
  • cargo test -p codewhale-tui --lib chatgpt_oauth (10 tests: PKCE S256, callback success/error/state, mock token exchange, persist/refresh/revoke, no live OpenAI)
  • cargo test -p codewhale-tui --lib -- provider_picker::tests (126)
  • cargo test -p codewhale-cli --lib -- parses_auth_subcommand_matrix auth_status
  • python3 scripts/check-tui-locale-parity.py
  • Full cargo test --workspace --all-features --locked not run locally (TUI crate compile is large; CI will cover)
  • TUI browser login not live-tested against OpenAI in this environment

Unit tests use a mock issuer. They do not hit live OpenAI.

Checklist

  • This PR adds a provider adapter (chatgpt_oauth.rs), not a product fork; it reuses the existing Codewhale-owned credentials directory
  • Updated docs (docs/PROVIDERS.md, docs/CONFIGURATION.md)
  • Added or updated tests where relevant
  • Verified TUI behavior manually if UI changes (picker copy + slash/CLI wiring covered by tests; live PKCE not run)
  • Harvested/co-authored credit uses a GitHub numeric noreply address (N/A — CodeWhale Bot)

Note

High Risk
Introduces OAuth token lifecycle, loopback callback handling, and global logout/revoke paths for a billing-critical provider; mistakes could leak credentials, deadlock logout, or break Codex access.

Overview
Adds Sign in with ChatGPT as a first-class way to use the openai-codex route: browser PKCE + localhost callback (ports 1455/1457), Codewhale-owned token files under $CODEWHALE_HOME/credentials, and honest originator=codewhale. Codex CLI import stays an explicit, read-only alternative—not a prerequisite.

CLI/TUI: codewhale auth chatgpt and auth chatgpt-revoke (plus /auth chatgpt and /auth chatgpt-revoke) wire through the same flows as xAI device login. Global logout clears ChatGPT OAuth config pointers and credential files via a non-reentrant clear_all_chatgpt_oauth_credentials_locked so nested lifecycle locks do not hang.

Runtime: OpenAI Codex credential order becomes env → Codewhale-owned ChatGPT sign-in (with refresh under lock) → consent-gated Codex CLI file. auth status, credential resolution, setup copy, and provider picker onboarding reflect that order; the picker adds a ChatGPT vs Import Codex CLI stage before external consent.

Config crate: Shared OAuth generation validation/paths/clear helpers for chatgpt-auth-<hex>.json alongside existing xAI storage in the credentials directory.

Reviewed by Cursor Bugbot for commit f59eb47. Bugbot is set up for automated code reviews on this repo. Configure here.

Add a Codewhale-owned Sign in with ChatGPT adapter so the openai-codex
route no longer requires Codex CLI. Browser PKCE uses the published
auth.openai.com issuer, stores rotating refresh tokens in the existing
credentials directory, and keeps external CLI import as an explicit
alternative. Device-code is omitted because the issuer does not
advertise device_authorization_endpoint.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@claude

claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Reviewing PR #5784 — native ChatGPT PKCE sign-in for openai-codex

Todo list

  • Gather context (PR diff, changed files, CLAUDE.md/AGENTS.md rules)
  • Understand request (code review requested)
  • Review crates/tui/src/chatgpt_oauth.rs (new PKCE/OAuth logic)
  • Review crates/config/src/xai_credentials.rs changes (shared credential storage)
  • Review provider/route wiring (config.rs, credential_resolve.rs, provider_picker.rs, route_runtime.rs)
  • Review CLI/command wiring (cli/src/lib.rs, commands/*)
  • Check for provider/model-prefix inference violations (EPIC v0.8.65 EPIC: Separate provider facts, model facts, offerings, and route resolution #2608 invariant)
  • Check tests and locale parity
  • Post final review

View job run

@Hmbown
Hmbown enabled auto-merge (squash) August 31, 2026 20:43
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@claude

claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 3m 42s —— View job


Reviewing PR #5784 — native ChatGPT PKCE sign-in for openai-codex

Todo list

  • Gather context (PR diff, changed files, CLAUDE.md/AGENTS.md rules)
  • Understand request (code review requested)
  • Review crates/tui/src/chatgpt_oauth.rs (new PKCE/OAuth logic)
  • Review crates/config/src/xai_credentials.rs changes (shared credential storage)
  • Review provider/route wiring (config.rs, credential_resolve.rs, provider_picker.rs, route_runtime.rs)
  • Review CLI/command wiring (cli/src/lib.rs, commands/*)
  • Check for provider/model-prefix inference violations (EPIC v0.8.65 EPIC: Separate provider facts, model facts, offerings, and route resolution #2608 invariant)
  • Check tests and locale parity
  • Post final review

Summary

Solid, well-scoped adapter that closely mirrors the existing xAI device-login pattern (shared XaiOAuthCredentialStore/lifecycle lock, explicit-choice picker stage, redacted Debug impls, credential precedence env → Codewhale-owned store → consent-gated Codex CLI). No EPIC #2608 provider-inference violations found — the ChatGPT route is only entered via explicit picker/CLI/slash-command choice, never inferred from a model string prefix. Secrets are consistently redacted in Debug impls and error messages (token_exchange_error_does_not_echo_body_secrets test confirms this). Locale parity is intact across all 15 non-English locales.

Findings

1. Blocking network I/O runs synchronously inside the async TUI event loopcrates/tui/src/commands/groups/config/mod.rs:191-198
/auth chatgpt-revoke calls crate::chatgpt_oauth::revoke_owned_login(None, None) directly and synchronously. That function does a blocking reqwest POST per token (OAUTH_REQUEST_TIMEOUT = 20s) via revoke_remote_token. commands::execute is invoked without spawning from execute_command_input (crates/tui/src/tui/ui.rs:943), an async fn on the shared tokio runtime — unlike the sign-in path, which correctly does pause_terminal + tokio::task::spawn_blocking (run_chatgpt_pkce_login_from_tui, crates/tui/src/tui/ui/event_loop.rs:6249). A slow/hanging revoke endpoint will visibly freeze the TUI's render/input loop for up to the request timeout. There's no prior /auth *-revoke slash command to have copied this from — worth routing this through an AppAction/spawn_blocking like the login flow.
Fix this →

2. Doc claims OIDC-discovery-based revoke, but the endpoint is hardcodedcrates/tui/src/chatgpt_oauth.rs:13-14,24,284-286
The module doc and the PR body both state the adapter "uses the discovery revocation_endpoint for remote revoke," but revoke_endpoint() just formats a hardcoded /api/accounts/oauth/revoke path — there's no code anywhere in this diff that fetches https://auth.openai.com/.well-known/openid-configuration or reads a revocation_endpoint field. Given this PR's terms-boundary argument leans heavily on "only publishes documented, discoverable endpoints," the doc comment overstates what the code does. Either implement actual discovery or correct the comment to say the revoke path is a fixed convention, not one read from discovery.

3. Minor: unused localized messagecrates/tui/src/localization.rs:1664 (+15 locale files)
MessageId::ProviderExternalHintChatgptReview is declared, added to ALL_MESSAGE_IDS, and translated into all 15 locales, but nothing in provider_picker.rs (or elsewhere) references it — only ProviderExternalActionReuseCodex is used for the "E" hint in render_chatgpt_auth_choice. Either wire it into the UI or drop it to avoid carrying translator effort for unreachable copy.

Good choices worth calling out

  • Explicit two-step picker (ChatgptAuthChoice::SignInWithChatgpt vs ImportCodexCli) keeps the two credential owners (Codewhale-owned vs Codex CLI) from being conflated, consistent with the xAI pattern.
  • state is checked on both success and (when present) error callbacks (accept_callback), and PKCE verifier/challenge are S256 per RFC 7636, covered by pkce_verifier_and_challenge_are_s256.
  • parse_token_response fails closed and tells the user to re-auth on invalid_grant/refresh_token_reused/refresh_token_expired/refresh_token_invalidated/401, matching the "rotating refresh tokens fail closed" claim in the PR body.
  • Callback error bodies never echo error_description-adjacent secrets back into logs/errors (token_exchange_error_does_not_echo_body_secrets).

@codewhale-agent codewhale-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Codewhale review

Adds native Sign in with ChatGPT PKCE loopback flow for openai-codex, including config storage, CLI/TUI wiring, localization, tests, and docs. Overall solid but has several correctness concerns around loopback host resolution, hardcoded revocation endpoint versus the discovery claim, and opaque token expiry handling.

Findings

  • [WARNING] Loopback callback uses localhost redirect while listener binds 127.0.0.1 (crates/tui/src/chatgpt_oauth.rs)
    bind_loopback_callback() binds only to 127.0.0.1, but redirect_uri_for_port() returns http://localhost:/auth/callback. On systems where localhost resolves to ::1 first (common on Linux with IPv6 enabled), the browser may connect to ::1 and be refused, breaking PKCE login. Bind to localhost (both v4/v6) or align the redirect URI to the bound address, and verify against OpenAI's registered redirect URIs.
  • [WARNING] Revocation endpoint hardcoded, not read from OIDC discovery (crates/tui/src/chatgpt_oauth.rs)
    PR claims to use the discovery revocation_endpoint, but revoke_endpoint() hardcodes /api/accounts/oauth/revoke and pkce_login() never fetches https://auth.openai.com/.well-known/openid-configuration. If OpenAI rotates endpoints, revoke will fail while the rest of the flow may still work. Fetch and use the advertised revocation_endpoint, or update docs to say it is hardcoded.
  • [INFO] select_entry prefers built-in client id even when custom client id is configured (crates/tui/src/chatgpt_oauth.rs)
    select_entry always prefers keys ending with ::app_EMo... (CHATGPT_OAUTH_CLIENT_ID). When CODEWHALE_CHATGPT_OAUTH_CLIENT_ID is set to a different client id, the current entry won't be preferred, and fallback may pick an older entry. Consider deriving the preferred suffix from the entry/config client id.
  • [WARNING] Opaque access tokens without expires_in are treated as expired and can fail closed incorrectly (crates/tui/src/chatgpt_oauth.rs)
    entry_access_token_is_fresh returns false when neither expires_at nor a JWT exp is present. For valid opaque tokens issued without expires_in (or with no JWT), get_owned_credentials_locked will attempt refresh and, if no refresh token is stored, error 'access token expired and no refresh_token is stored' even though the access token may be valid. Consider treating missing expiry as fresh until a 401 indicates otherwise, or require and document that the issuer always returns expires_in.
  • [WARNING] Local credential file removal errors are silently ignored during revoke (crates/tui/src/chatgpt_oauth.rs)
    In revoke_owned_login_locked, store.remove(&name) uses let _ = store.remove(&name);, so a failed deletion (permissions, I/O) leaves the token file on disk while the command reports success and the config pointer is unset. The user may believe credentials are revoked. Propagate or at least log the error.
  • [INFO] Logout clears only valid generation pointer, not legacy chatgpt pointer (crates/cli/src/lib.rs)
    run_logout_command_with_secrets_unlocked sets oauth_credential_generation to None only if is_valid_chatgpt_oauth_generation. If the config pointer somehow holds the legacy name chatgpt-oauth.json, logout will not clear it, then clear_all_chatgpt_oauth_credentials deletes that file, leaving a dangling config pointer. Use is_valid_chatgpt_oauth_generation or also check LEGACY_CHATGPT_OAUTH_FILE_NAME.

Suggestions

  • crates/tui/src/chatgpt_oauth.rs — Bind the callback listener to localhost as well as 127.0.0.1, or change redirect_uri_for_port to use http://127.0.0.1:<port> so the browser connects to the bound interface. Verify that the chosen URI is among the public client's registered redirect URIs.
  • crates/tui/src/chatgpt_oauth.rs — Fetch the OIDC discovery document on first use and cache the advertised revocation_endpoint instead of hardcoding it, so remote revoke stays aligned with issuer configuration. If hardcoding is intentional, update docs and PR text accordingly.
  • crates/tui/src/chatgpt_oauth.rs — Replace let _ = store.remove(&name); with logging or propagate the error so users are not misled when local credential deletion fails.

Assessment

The PR is well-structured with good tests and documentation, but a few correctness issues should be addressed before merge: the localhost/IPv4 callback mismatch can break login on many systems, revocation endpoint is hardcoded despite the discovery claim, and opaque-token expiry handling may fail closed incorrectly. The code otherwise reuses patterns well and keeps token material redacted.


Advisory review by Codewhale (codewhale review --pr 5784 --post, head c65d0ea1bacfa54d0dc419985d082dadc3a2c9b8). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.

Comment thread crates/tui/src/chatgpt_oauth.rs Dismissed
Comment thread crates/tui/src/chatgpt_oauth.rs Dismissed
@Hmbown
Hmbown disabled auto-merge September 1, 2026 01:58
The redirect URI has to keep saying `localhost` -- that exact string is what
the public Codex client is registered against and redirect matching is exact
-- but `localhost` resolves to `::1` as readily as to `127.0.0.1`, and an
IPv6-first browser is free to prefer it. Binding only `127.0.0.1` sent that
browser to a closed port: Happy Eyeballs turns a working sign-in into a slow
one, and where that fallback is off, into a hang until the callback timeout.

So bind both families on the chosen port and poll both; a host with only one
stack binds that one and still signs in. The redirect URI is untouched.

The module doc also claimed revoke used the discovery `revocation_endpoint`
while revoke_endpoint() hardcodes `/api/accounts/oauth/revoke`. The hardcoded
path is deliberate -- revoke has to clear local credentials even when the
issuer is unreachable, and a discovery fetch would only add a failure mode to
a cleanup path -- so the doc now says that instead of describing code that was
never written.

New test drives a real callback into the IPv6 listener with an idle IPv4
listener ahead of it in the poll order, so it fails if either the bind or the
polling loop regresses to one family:

  cargo test -p codewhale-tui --lib chatgpt_oauth
  test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 11659 filtered out

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Copilot AI lite review requested due to automatic review settings September 1, 2026 02:30
@cursor

cursor Bot commented Sep 1, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_643a570a-e2d4-4e3d-adfc-32c4f47e765d)

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale comment

Not approving: Cursor Bugbot was present on this head but completed as skipped (usage limit / incomplete analysis), so the required automated-review signal is not clean. Human review is needed; reviewers were assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

Copilot AI 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.

🟡 Changes recommended

There are confirmed security/correctness issues in the loopback callback binding logic and /auth chatgpt-revoke config targeting that should be fixed before merge.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR introduces a native Sign in with ChatGPT flow for the first-class openai-codex route in the TUI/CLI, implementing an authorization-code PKCE (S256) browser login with a localhost callback and storing refreshable tokens in Codewhale-owned credentials storage, while keeping the Codex CLI import path as an explicit alternative.

Changes:

  • Add a new chatgpt_oauth adapter implementing PKCE loopback login, token persistence, refresh, and best-effort revoke for openai-codex.
  • Wire new auth entrypoints into CLI + TUI (codewhale auth chatgpt, /auth chatgpt, provider setup picker flow) and update credential resolution precedence.
  • Update docs, changelogs, provider guidance, and TUI locale packs to reflect the new ChatGPT subscription billing/auth path.
File summaries
File Description
docs/PROVIDERS.md Documents native ChatGPT PKCE sign-in and clarifies billing-owner distinctions for openai-codex.
docs/CONFIGURATION.md Updates credential precedence text for openai-codex to include Codewhale-owned ChatGPT tokens.
crates/tui/src/tui/views/mod.rs Adds a new view event for ChatGPT PKCE sign-in requests from the provider picker.
crates/tui/src/tui/ui/handlers.rs Handles the new provider picker ChatGPT PKCE event and triggers the TUI login flow.
crates/tui/src/tui/ui/event_loop.rs Implements run_chatgpt_pkce_login_from_tui with terminal pause/resume around the login flow.
crates/tui/src/tui/ui/apply.rs Adds an AppAction for starting ChatGPT PKCE login and applies activation + provider switching.
crates/tui/src/tui/setup/mod.rs Updates setup guidance messaging for openai-codex to prefer ChatGPT sign-in.
crates/tui/src/tui/provider_picker.rs Adds a ChatGPT/Codex auth-choice stage (ChatGPT PKCE vs Codex CLI import) plus tests.
crates/tui/src/tui/app/types.rs Adds StartChatgptPkceLogin AppAction.
crates/tui/src/route_runtime.rs Updates missing-auth “next step” guidance for openai-codex to prefer ChatGPT sign-in.
crates/tui/src/oauth.rs Routes the openai-codex missing-auth message through the new chatgpt_oauth messaging.
crates/tui/src/localization.rs Adds new MessageIds for the ChatGPT auth-choice UI strings.
crates/tui/src/lib.rs Adds auth chatgpt / auth chatgpt-revoke CLI subcommands in the TUI binary.
crates/tui/src/config/credential_resolve.rs Surfaces “Codewhale-owned ChatGPT sign-in” as an OAuth credential probe for openai-codex.
crates/tui/src/config.rs Adds config helpers and codex_credentials() resolution support for owned ChatGPT OAuth credentials.
crates/tui/src/commands/mod.rs Adds a test ensuring /auth chatgpt starts the login action.
crates/tui/src/commands/groups/core/core.rs Updates provider help output tests to include codewhale auth chatgpt.
crates/tui/src/commands/groups/config/mod.rs Adds /auth chatgpt and /auth chatgpt-revoke command routing.
crates/tui/src/chatgpt_oauth.rs New module implementing ChatGPT/Codex subscription PKCE loopback login, storage, refresh, and revoke.
crates/tui/locales/en.json Adds localized strings for ChatGPT auth-choice UI and actions (EN).
crates/tui/locales/de.json Adds localized strings for ChatGPT auth-choice UI and actions (DE).
crates/tui/locales/ca.json Adds localized strings for ChatGPT auth-choice UI and actions (CA).
crates/tui/locales/es-419.json Adds localized strings for ChatGPT auth-choice UI and actions (es-419).
crates/tui/locales/fr.json Adds localized strings for ChatGPT auth-choice UI and actions (FR).
crates/tui/locales/hi.json Adds localized strings for ChatGPT auth-choice UI and actions (HI).
crates/tui/locales/id.json Adds localized strings for ChatGPT auth-choice UI and actions (ID).
crates/tui/locales/ja.json Adds localized strings for ChatGPT auth-choice UI and actions (JA).
crates/tui/locales/ko.json Adds localized strings for ChatGPT auth-choice UI and actions (KO).
crates/tui/locales/pt-BR.json Adds localized strings for ChatGPT auth-choice UI and actions (pt-BR).
crates/tui/locales/ru.json Adds localized strings for ChatGPT auth-choice UI and actions (RU).
crates/tui/locales/uk.json Adds localized strings for ChatGPT auth-choice UI and actions (UK).
crates/tui/locales/vi.json Adds localized strings for ChatGPT auth-choice UI and actions (VI).
crates/tui/locales/zh-Hans.json Adds localized strings for ChatGPT auth-choice UI and actions (zh-Hans).
crates/tui/locales/zh-Hant.json Adds localized strings for ChatGPT auth-choice UI and actions (zh-Hant).
crates/tui/CHANGELOG.md Notes the new native ChatGPT sign-in option for openai-codex.
crates/config/src/xai_credentials.rs Extends owned-credential store infrastructure to support ChatGPT OAuth generations and cleanup.
crates/config/src/provider.rs Updates openai-codex credential help guidance to prefer ChatGPT sign-in.
crates/config/src/lib.rs Re-exports new ChatGPT OAuth credential store helpers and validation functions.
crates/cli/src/lib.rs Adds codewhale auth chatgpt / chatgpt-revoke, updates logout cleanup and auth status output.
CHANGELOG.md Notes the new native ChatGPT sign-in option for openai-codex.
Review details
  • Files reviewed: 40/40 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +441 to +461
let mut last_error = None;
for port in CHATGPT_OAUTH_LOOPBACK_PORTS {
let mut bound = Vec::new();
for addr in [
SocketAddr::from((Ipv4Addr::LOCALHOST, port)),
SocketAddr::from((Ipv6Addr::LOCALHOST, port)),
] {
match TcpListener::bind(addr) {
Ok(listener) => {
listener
.set_nonblocking(true)
.context("ChatGPT OAuth callback listener could not be set non-blocking")?;
bound.push(listener);
}
Err(error) => last_error = Some(error),
}
}
if !bound.is_empty() {
return Ok(bound);
}
}
Comment on lines +191 to +193
Some("chatgpt-revoke") | Some("chatgpt_revoke") => {
match crate::chatgpt_oauth::revoke_owned_login(None, None) {
Ok(()) => CommandResult::message(
Comment on lines +191 to +195
Some("chatgpt-revoke") | Some("chatgpt_revoke") => {
match crate::chatgpt_oauth::revoke_owned_login(None, None) {
Ok(()) => CommandResult::message(
"Revoked Codewhale-owned ChatGPT tokens. Codex CLI consent is unchanged.",
),
…kce-5778-20260831

# Conflicts:
#	CHANGELOG.md
#	crates/tui/CHANGELOG.md
#	crates/tui/src/config.rs
@cursor

cursor Bot commented Sep 1, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_9d2d4fe3-7469-4ffd-8566-e2fc96939816)

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale comment

Not approving: Cursor Bugbot was present on this head but completed as skipped (usage limit / incomplete analysis), so the required automated-review signal is not clean. Human review is needed; reviewers were assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@claude

claude Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 2m 2s —— View job


Reviewing PR #5784 — native ChatGPT PKCE sign-in (re-review after push)

Todo list

  • Gather context (PR diff vs origin/main, prior review threads, CLAUDE.md/AGENTS.md rules)
  • Determine what changed since the last two claude reviews (commit 81b5f7a — loopback fix)
  • Check whether prior findings (loopback bind, revoke hardcode doc, opaque-token expiry, silent remove errors, blocking revoke in TUI, config-path targeting) are resolved
  • Do a fresh pass for new issues introduced by 81b5f7a
  • Post final review

What changed since the last review

Commit 81b5f7a fixed two of the items raised earlier:

  • bind_loopback_callback() now binds both 127.0.0.1 and ::1 on the chosen port and wait_for_callback polls all bound listeners (crates/tui/src/chatgpt_oauth.rs:440-495), closing the "browser resolves localhost to ::1, gets refused" failure mode flagged by the earlier codewhale-agent review. New test callback_is_accepted_on_either_loopback_family covers it.
  • The module doc and revoke_owned_login doc comment no longer claim OIDC-discovery-based revoke; they now correctly describe revoke_endpoint() as an intentionally hardcoded convention (crates/tui/src/chatgpt_oauth.rs:24-29, :801-807).

Good fix, and the reasoning in the commit message (revoke must clear local state even if discovery/issuer is unreachable) is sound.

Findings still open from prior reviews (unaddressed across this push)

  • /auth chatgpt-revoke still blocks the TUI event loop and targets the wrong configcrates/tui/src/commands/groups/config/mod.rs:191-197. It calls chatgpt_oauth::revoke_owned_login(None, None) synchronously inside dispatch, which is reached from the async execute_command_input without a spawn_blocking hop, unlike the sign-in path (run_chatgpt_pkce_login_from_tui, event_loop.rs) which correctly pauses the terminal and runs on spawn_blocking. revoke_owned_login does a blocking reqwest POST per stored token (20s timeout) while holding the OAuth lifecycle lock — a slow/hanging revoke endpoint freezes rendering/input. Separately, passing None for config_path always resolves the default config path/profile, not the one the running session loaded (App has config_path: Option<PathBuf>, and activate_pkce_login for the login path is correctly called with app.config_path.as_deref(), e.g. apply.rs:2914 area) — so in a session started with a non-default config/profile, /auth chatgpt-revoke can revoke or unset the pointer in a different config file than the one active. Both issues were flagged by the earlier Claude review and Copilot review; neither is fixed yet.
    Fix this →

  • Local credential removal errors during revoke are still silently swallowedcrates/tui/src/chatgpt_oauth.rs:875: let _ = store.remove(&name);. If deletion fails (permissions, I/O), the token file stays on disk, the config pointer is unset, and the command reports success — the user believes they've revoked access when the secret is still present on disk.

  • Opaque access tokens without expires_in/JWT exp are still treated as always-expiredentry_access_token_is_fresh (around crates/tui/src/chatgpt_oauth.rs:1073-1090 in the pre-fix line numbering, now shifted ~+80 lines) returns false whenever neither expires_at nor a JWT exp is present, even if the access token itself is still valid. get_owned_credentials_locked will then try to refresh, and fails closed with "access token expired and no refresh_token is stored" if no refresh token happens to be present — a valid opaque token gets treated as unusable.

  • select_entry still hardcodes preference for the built-in client id (crates/tui/src/chatgpt_oauth.rs:1085-1096), not the configured CODEWHALE_CHATGPT_OAUTH_CLIENT_ID. Low impact today since that env var isn't normally set, but the preference silently stops matching if it ever is.

  • Logout still leaves a dangling pointer for the legacy generation namecrates/cli/src/lib.rs:2360-2370 only clears oauth_credential_generation when it passes is_valid_chatgpt_oauth_generation. If the pointer holds the legacy chatgpt-oauth.json name, clear_all_chatgpt_oauth_credentials() deletes the file but the config pointer is left set to a now-missing file.

  • Unused localized message persists: MessageId::ProviderExternalHintChatgptReview (crates/tui/src/localization.rs:1664, +15 locale files) is still declared/translated but never referenced in provider_picker.rs or elsewhere — only ProviderExternalActionReuseCodex is used for the "E" hint.

New observation on the loopback fix itself

bind_loopback_callback() (crates/tui/src/chatgpt_oauth.rs:440-468) returns success as soon as either the IPv4 or IPv6 bind on a port succeeds — it doesn't treat a partial bind (one family conflicts with AddrInUse from an unrelated local process) as "port unavailable" and move to the next port. If another process already holds 127.0.0.1:1455 while this process only manages to bind ::1:1455, the registered localhost:1455 redirect can still resolve to the occupied IPv4 socket on some hosts/browsers, delivering the OAuth code to the wrong process instead of failing over to port 1457. This was flagged inline by the Copilot review against this same (already-fixed) code and is still open — worth treating any single-family bind failure the same way full failure is treated (close what was bound, try the next port) rather than accepting a partial bind as good enough.

Assessment

The loopback dual-stack fix and the revoke-endpoint doc correction in 81b5f7a are solid, well-reasoned, and covered by a real test. However, five issues flagged in earlier review rounds on this same PR (blocking /auth chatgpt-revoke, wrong config-path targeting on revoke, silent local-delete failures, opaque-token expiry fail-closed, dangling legacy pointer on logout) remain unaddressed across two pushes, plus one new-since-fix issue (partial-bind port squatting risk). None of these are blockers for the terms/PKCE design itself, but the /auth chatgpt-revoke item combines a UI-freeze bug with a wrong-config-file bug and should be fixed before merge; the rest are correctness/robustness gaps worth tracking even if not release-blocking.

@codewhale-agent codewhale-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Codewhale review

PR adds native ChatGPT PKCE sign-in for openai-codex with Codewhale-owned token storage, refresh, and revoke. The implementation is careful (PKCE S256, state checks, redacted Debug, mock issuer tests), but the revoke path has correctness gaps and the TUI slash-command revoke leaves in-memory state stale.

Findings

  • [WARNING] /auth chatgpt-revoke does not update the live TUI config (crates/tui/src/commands/groups/config/mod.rs:192)
    The slash-command handler calls revoke_owned_login(None, None), which writes the config file but does not clear the in-memory App.config. After revoke, the TUI still has the ChatGPT generation pointer and can show stale auth status until restart/reload. Pass the live config as the second argument.
  • [WARNING] Remote revoke only revokes one token per entry and ignores local-removal errors (crates/tui/src/chatgpt_oauth.rs)
    In revoke_owned_login_locked, each entry revokes refresh_token.as_deref().or(access_token.as_deref()); when both are present the access token is left valid server-side. Local removal uses let _ = store.remove(&name);, so a failed delete still returns Ok(()). Revoking is security-sensitive and should attempt to revoke both tokens and propagate or log local removal failures.
  • [INFO] Hard-coded revocation endpoint contradicts OIDC discovery and PR description (crates/tui/src/chatgpt_oauth.rs)
    The PR description states the discovery revocation_endpoint is used for remote revoke, but revoke_endpoint() hard-codes {issuer}/api/accounts/oauth/revoke and the comment says discovery is deliberately not consulted. If OpenAI moves that endpoint, local revoke still works but remote revoke silently breaks. Consider reading the advertised endpoint or documenting the pinned contract more clearly.
  • [INFO] Unused localization key ProviderExternalHintChatgptReview (crates/tui/src/tui/provider_picker.rs)
    The new message id and all locale translations are added, but the ChatGPT auth choice screen never renders ProviderExternalHintChatgptReview. The intended hint is therefore never shown. Use it in render_chatgpt_auth_choice or remove the key/translations to avoid dead UI copy.
  • [INFO] Global logout clears local ChatGPT tokens but does not call remote revoke (crates/cli/src/lib.rs)
    clear_all_chatgpt_oauth_credentials() only deletes local Codewhale-owned files. A user pressing global logout may expect revocation, but refresh/access tokens can remain valid server-side until expiry. Consider invoking best-effort remote revoke before clearing or explicitly documenting that logout only clears local state.

Suggestions

  • crates/tui/src/commands/groups/config/mod.rs:192 — Pass the live app config to revoke_owned_login so the in-memory TUI state is cleared when the owned credential generation is removed.

                    match crate::chatgpt_oauth::revoke_owned_login(None, Some(&mut app.config)) {
    

Assessment

The PKCE implementation is careful and mostly well-tested. The main gaps are revoke correctness (only one token revoked, local removal errors ignored) and stale in-memory TUI state after the slash-command revoke. The legal and endpoint assumptions are documented but should be explicitly accepted before merge.


Advisory review by Codewhale (codewhale review --pr 5784 --post, head f56fce794c0de72478048468b2ea830dd66ed0b3). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.

CommandResult::action(crate::tui::app::AppAction::StartChatgptPkceLogin)
}
Some("chatgpt-revoke") | Some("chatgpt_revoke") => {
match crate::chatgpt_oauth::revoke_owned_login(None, None) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[WARNING] /auth chatgpt-revoke does not update the live TUI config

The slash-command handler calls revoke_owned_login(None, None), which writes the config file but does not clear the in-memory App.config. After revoke, the TUI still has the ChatGPT generation pointer and can show stale auth status until restart/reload. Pass the live config as the second argument.

CommandResult::action(crate::tui::app::AppAction::StartChatgptPkceLogin)
}
Some("chatgpt-revoke") | Some("chatgpt_revoke") => {
match crate::chatgpt_oauth::revoke_owned_login(None, None) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pass the live app config to revoke_owned_login so the in-memory TUI state is cleared when the owned credential generation is removed.

Suggested change
match crate::chatgpt_oauth::revoke_owned_login(None, None) {
match crate::chatgpt_oauth::revoke_owned_login(None, Some(&mut app.config)) {

@cursor

cursor Bot commented Sep 1, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_02e261f5-9112-4c7e-ae41-f3bd3fdcd9c3)

@claude

claude Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 1m 4s —— View job


Reviewing PR #5784 — native ChatGPT PKCE sign-in (re-review after latest push)

Todo list

  • Gather context (diff vs origin/main, prior review threads, CLAUDE.md/AGENTS.md rules)
  • Determine what changed since the last review round
  • Check whether prior findings are resolved
  • Fresh pass for new issues
  • Post final review

What changed since the last review

Nothing in the ChatGPT OAuth surface. git diff f56fce7..HEAD (the commit the last codewhale-agent review covered) touches exactly one file, and it's unrelated to this feature:

crates/tui/tests/integration/exec_persistent_service.rs | 16 ++++++++++++++--

The rest of the delta since then is just a merge of origin/main (0e51dd4) plus that one test-ordering fix (686b4f9, for exec_persistent_service, not ChatGPT auth). crates/tui/src/chatgpt_oauth.rs, crates/tui/src/commands/groups/config/mod.rs, crates/cli/src/lib.rs, and crates/config/src/xai_credentials.rs are byte-for-byte unchanged since the last two review rounds on this PR.

Status of previously flagged findings — all still open

Verified directly against current HEAD (0e51dd4):

  • /auth chatgpt-revoke still blocks the TUI event loop, targets the wrong config, and doesn't update live statecrates/tui/src/commands/groups/config/mod.rs:191-197 still calls crate::chatgpt_oauth::revoke_owned_login(None, None) synchronously from the async command dispatcher, with None for both config_path (so it always resolves the default config, not the session's active app.config_path) and live_config (so App.config's in-memory generation pointer stays stale until restart/reload). The sign-in path correctly does spawn_blocking + passes app.config_path; revoke doesn't. Two separate reviews (claude, codewhale-agent) and a Copilot review have flagged this across three pushes now.
    Fix this →
  • Local credential removal errors during revoke are silently swallowedcrates/tui/src/chatgpt_oauth.rs:875: let _ = store.remove(&name);. A failed delete (permissions, I/O) leaves the secret on disk while the command reports success.
  • Revoke only revokes one token per entryrevoke_remote_token is called with refresh_token.or(access_token); when both exist, the access token is left valid server-side.
  • Opaque access tokens without expires_in/JWT exp are still treated as always-expiredentry_access_token_is_fresh (crates/tui/src/chatgpt_oauth.rs:~868) returns false whenever neither expires_at nor a JWT exp is present, so a still-valid opaque token forces a refresh attempt and fails closed if no refresh token happens to be stored.
  • Logout can leave a dangling pointer for the legacy generation namecrates/cli/src/lib.rs:2367 (and :3595) only clears oauth_credential_generation via is_valid_chatgpt_oauth_generation; a pointer holding the legacy chatgpt-oauth.json name isn't cleared even though clear_all_chatgpt_oauth_credentials() deletes that file.
  • Unused localized messageMessageId::ProviderExternalHintChatgptReview (crates/tui/src/localization.rs:1664, :3650, +15 locale files) is still declared/translated but never referenced in provider_picker.rs.
  • Partial-bind loopback risk (flagged by Copilot against 81b5f7a's dual-stack fix): bind_loopback_callback() accepts a port as soon as either IPv4 or IPv6 binds — if another local process already holds one family, this process can silently bind only the other, and depending on host/browser resolution order the OAuth code could be delivered to the wrong process instead of failing over to port 1457.

None of these are new; they were raised across the two prior review rounds on this same head and remain unaddressed since no commits touched the affected files. The /auth chatgpt-revoke item (UI freeze + wrong-config-file targeting + stale in-memory state) is the one worth resolving before merge — the rest are correctness/robustness gaps that can be tracked but are lower severity.

Good design still holds

The core PKCE flow itself (S256 challenge, state verification on both success/error callbacks, redacted Debug impls, fail-closed handling of invalid_grant/refresh_token_reused/etc., dual-stack loopback bind for the common case, honest originator=codewhale) is solid and unchanged from the earlier reviews' assessment.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale comment

Not approving: Cursor Bugbot was present on this head but completed as skipped (usage limit / incomplete analysis), so the required automated-review signal is not clean. Human review is needed; no additional reviewers could be assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@codewhale-agent codewhale-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Codewhale review

PR adds native ChatGPT PKCE sign-in for openai-codex, with token storage, refresh, revoke, credential precedence, and UI integration. The implementation is mostly solid with good test coverage, but several issues around UI blocking, state consistency, and code duplication need attention.

Findings

  • [WARNING] Slash command /auth chatgpt-revoke performs blocking network I/O and does not update live config (crates/tui/src/commands/groups/config/mod.rs)
    In crates/tui/src/commands/groups/config/mod.rs, the handler for 'chatgpt-revoke' directly calls chatgpt_oauth::revoke_owned_login(None, None). This executes synchronously in the event loop, potentially blocking the TUI for up to 20 seconds during the best-effort remote revoke. Additionally, it does not update the in-memory app Config, leaving stale oauth_credential_generation and auth_mode values that may mislead the UI until a reload.
  • [WARNING] Logout clears ChatGPT generation but not auth_mode for openai-codex (crates/cli/src/lib.rs)
    In run_logout_command_with_secrets_unlocked (crates/cli/src/lib.rs), after clearing xai.auth_mode, the code only clears openai_codex.oauth_credential_generation if it is a valid ChatGPT generation, but does not set openai_codex.auth_mode = None. This leaves auth_mode as 'oauth' after logout, inconsistent with the xai path and potentially confusing later credential resolution or UI.
  • [WARNING] clear_codewhale_owned_chatgpt_oauth does not clear auth_mode (crates/tui/src/config.rs)
    In crates/tui/src/config.rs, the method clear_codewhale_owned_chatgpt_oauth only clears oauth_credential_generation but leaves auth_mode set to 'oauth'. This is also used during revoke in the TUI, so after revoking, the provider's auth_mode remains 'oauth' even though no owned OAuth tokens exist. This may cause misleading status displays or behavior in other parts of the app that check auth_mode.
  • [WARNING] Dead code for ChatGPT auth in CLI dispatch (crates/cli/src/lib.rs)
    In crates/cli/src/lib.rs, the match on AuthCommand in run() already handles AuthCommand::Chatgpt and AuthCommand::ChatgptRevoke (around line 1996), returning early. However, the later match inside run_auth_command_with_secrets_and_runtime (around line 3925) also contains arms for these variants, which are unreachable. This redundancy is confusing and dead code.
  • [INFO] Revoke endpoint not using OIDC discovery (crates/tui/src/chatgpt_oauth.rs)
    The PR description states it uses the discovery 'revocation_endpoint' for remote revoke, but the code (revoke_endpoint in chatgpt_oauth.rs) hard-codes {issuer}/api/accounts/oauth/revoke and explicitly does not read the discovery document. This contradicts the description and could break if OpenAI changes the endpoint, but the code comment acknowledges the design choice.
  • [INFO] Duplicate credential presence functions (crates/tui/src/chatgpt_oauth.rs)
    In chatgpt_oauth.rs, credentials_present and credentials_valid have identical implementations; credentials_present simply delegates to credentials_valid. This is unnecessary duplication.
  • [INFO] Missing test for token refresh failure/rotation (crates/tui/src/chatgpt_oauth.rs)
    The PR tests the success path for token refresh, but there is no test covering permanent refresh failure (e.g., invalid_grant) or rotation behavior when the refresh token is updated or not returned. This is a security-sensitive area and would benefit from additional negative tests.

Suggestions

  • crates/cli/src/lib.rs — When clearing ChatGPT OAuth generation during logout, also set openai_codex.auth_mode = None to mirror the xAI cleanup and avoid stale auth_mode.
  • crates/tui/src/config.rs — In clear_codewhale_owned_chatgpt_oauth, consider setting auth_mode = None as well (or preserving only if another OAuth source exists) to keep in-memory state consistent after revoke.
  • crates/tui/src/commands/groups/config/mod.rs — Refactor the chatgpt-revoke command to dispatch an asynchronous action (similar to login) rather than calling revoke_owned_login synchronously. This prevents UI freezing and allows updating the live config immediately.
  • crates/cli/src/lib.rs — Remove the redundant arms for AuthCommand::Chatgpt and AuthCommand::ChatgptRevoke in run_auth_command_with_secrets_and_runtime, as they are handled earlier in run() and never reached.

Assessment

The PR is generally well-implemented with strong test coverage for the PKCE flow, storage, and UI integration. The main concerns are the synchronous and state-inconsistent revoke slash command, the incomplete logout cleanup, and some dead code. These should be addressed before merging to avoid UI blocking and ensure consistent state management.


Advisory review by Codewhale (codewhale review --pr 5784 --post, head 0e51dd439e34fa59deb1757498967347a6f3b424). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.

@cursor

cursor Bot commented Sep 1, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_1526c2d4-7de3-4081-b671-c5437f1d49a1)

@claude

claude Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 2m 7s —— View job


Reviewing PR #5784 — native ChatGPT PKCE sign-in (re-review after latest push)

Todo list

  • Gather context (diff vs origin/main, prior review threads, CLAUDE.md/AGENTS.md rules)
  • Determine what changed since the last review round (HEAD was 0e51dd4)
  • Check whether prior findings are resolved
  • Fresh pass for new issues
  • Post final review

What changed since the last review

Nothing in the PR's own diff. The two new commits (b18d1fd, b3d64ff) are a merge of origin/main into this branch that picks up an already-landed upstream test fix for exec_persistent_service.rs flakiness (tracked separately, not part of this feature) plus an unrelated auth list row-labeling fix in crates/cli/src/lib.rs. Neither file is in this PR's diff against origin/main — confirmed via git diff origin/main...HEAD --stat, which lists the same 40 files/2368+/64- as the PR description, and via git diff 0e51dd4..HEAD restricted to every file this PR owns (crates/tui/src/chatgpt_oauth.rs, crates/config/**, crates/tui/src/commands/**, crates/tui/src/config.rs, crates/tui/src/oauth.rs, crates/tui/src/tui/provider_picker.rs, crates/tui/src/tui/ui/**, docs/**, etc.), which returns empty — byte-for-byte unchanged.

Status of previously flagged findings — all still open

Since the feature files are unchanged since the last review round (head 0e51dd4), every finding raised across the prior claude / codewhale-agent / Copilot / CodeQL reviews on this PR still applies. Summarizing the outstanding items (see earlier review comments on this PR for full detail and fix links):

  • /auth chatgpt-revoke blocks the TUI event loop, targets the wrong config, and doesn't update live statecrates/tui/src/commands/groups/config/mod.rs:191-197 calls chatgpt_oauth::revoke_owned_login(None, None) synchronously (blocking 20s reqwest POST) from the async dispatcher, with None for both config_path (misses the session's active app.config_path) and live_config (leaves App.config's in-memory pointer stale). This is the one most reviewers agree should block merge.
  • Local credential removal errors during revoke are silently swallowedchatgpt_oauth.rs:875, let _ = store.remove(&name);.
  • Revoke only revokes one token per entry (refresh_token.or(access_token)), leaving the other valid server-side when both exist.
  • Opaque access tokens without expires_in/JWT exp are treated as always-expired, forcing an avoidable refresh/fail-closed.
  • Logout can leave a dangling generation pointer for the legacy chatgpt-oauth.json name (crates/cli/src/lib.rs), and doesn't clear openai_codex.auth_mode alongside the generation pointer.
  • select_entry hardcodes preference for the built-in client id, ignoring CODEWHALE_CHATGPT_OAUTH_CLIENT_ID if ever set.
  • Unused localized message MessageId::ProviderExternalHintChatgptReview (localization.rs + 15 locale files) — declared/translated but never rendered in provider_picker.rs.
  • Partial-bind loopback risk: bind_loopback_callback() now binds both IPv4/IPv6, which fixed the original localhost::1 breakage, but if only one family binds (the other already held by an unrelated process) it still accepts that as success rather than treating it like a full failure and trying the next port — same-port squatting could deliver the OAuth code to the wrong local process.
  • Minor/dead-code: credentials_present/credentials_valid are identical (one just delegates), and the AuthCommand::Chatgpt/ChatgptRevoke match arms inside run_auth_command_with_secrets_and_runtime (crates/cli/src/lib.rs:~3925) are unreachable — those variants already return earlier in run().

None of these are new — they were raised across three prior review rounds on this same feature code and remain unaddressed since no commits have touched the affected files.

Design that still holds up well

Core PKCE mechanics (S256 challenge/verifier, state checked on both success and error callbacks, redacted Debug impls throughout, fail-closed handling of invalid_grant/refresh_token_reused/refresh_token_expired/refresh_token_invalidated/401, honest originator=codewhale, dual-stack loopback bind for the common case) remain solid and unchanged.

Assessment

This push is CI/test-infrastructure maintenance only (picking up an upstream flaky-test fix) — it does not touch the ChatGPT OAuth feature. No new review is warranted for the feature itself; the /auth chatgpt-revoke blocking + wrong-config-file + stale-live-state issue is the one worth resolving before merge, with the rest tracked as correctness/robustness follow-ups.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale comment

Not approving: Cursor Bugbot was present on this head but completed as skipped (usage limit / incomplete analysis), so the required automated-review signal is not clean. Human review is needed; reviewers were assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@codewhale-agent codewhale-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Codewhale review

Adds native ChatGPT PKCE sign-in for openai-codex with Codewhale-owned token storage, CLI/TUI commands, and configuration plumbing. The design handles OAuth security well (PKCE S256, redaction, fail-closed refresh), but state cleanup after logout/revoke and remote revocation of replaced tokens have gaps, and the live flow is untested.

Findings

  • [WARNING] openai_codex auth_mode is not reset on logout or revoke (crates/cli/src/lib.rs)
    After a native ChatGPT login, Config::mark_codewhale_owned_chatgpt_oauth sets provider.auth_mode = "oauth". However, run_logout_command_with_secrets_unlocked only clears oauth_credential_generation and does not clear openai_codex.auth_mode; revoke_owned_login_locked/clear_codewhale_owned_chatgpt_oauth similarly only clear the generation. This leaves the provider configured for OAuth after credentials are removed, unlike the xAI path which resets both fields. Users may be unable to re-run setup cleanly or see stale auth-mode state.
  • [WARNING] Superseded ChatGPT tokens are removed without remote revocation (crates/tui/src/chatgpt_oauth.rs)
    activate_pkce_login_locked identifies previous_owned_name (legacy or prior generation) and removes it locally after installing the new generation, but never calls revoke_remote_token on the old refresh token/access token. If the previous refresh token was compromised, it remains valid remotely after the user re-authenticates. Best-effort remote revocation should happen before deleting the old file, or the old token should be explicitly revoked after successful activation.
  • [INFO] Callback HTTP server reads only a single 4096-byte chunk (crates/tui/src/chatgpt_oauth.rs)
    handle_callback_stream performs one read of a 4096-byte buffer and assumes the request line/headers are complete. Large error_description values or TCP segmentation can truncate the request and fail the callback. A loop reading until end-of-headers (or read_to_end) with the existing timeout would be more robust.
  • [INFO] Revoke path may report success while leaving remote tokens valid (crates/tui/src/chatgpt_oauth.rs)
    revoke_owned_login_locked parses the local file to revoke tokens; if parse_auth_file fails (corrupt file), it silently skips remote revocation, removes the local file, and returns Ok. The user sees success but the remote refresh token remains valid. At minimum it should warn/fail when it cannot revoke a credential it is deleting.
  • [INFO] Live OpenAI flow not actually exercised
    PR checklist notes TUI browser login was not live-tested against OpenAI, and full workspace tests were not run locally. The mocked unit tests cover PKCE mechanics but not real issuer behavior, redirect ports, or token refresh rotation. Consider a manual/CI integration test before merge.

Suggestions

  • crates/cli/src/lib.rs — In run_logout_command_with_secrets_unlocked, add openai_codex.auth_mode = None; after clearing the OAuth generation, and in Config::clear_codewhale_owned_chatgpt_oauth, also set entry.auth_mode = None when clearing generation so the provider does not remain in OAuth mode after logout/revoke.
  • crates/tui/src/chatgpt_oauth.rs — Before removing previous_owned_name in activate_pkce_login_locked, read its entries and call revoke_remote_token (best-effort) for each refresh/access token, logging failures, so replaced credentials are not left valid remotely.
  • crates/tui/src/chatgpt_oauth.rs — Modify handle_callback_stream to loop reads until the request headers are complete or a maximum size is reached, instead of a single read, to avoid truncation on split packets or large query strings.
  • crates/tui/src/chatgpt_oauth.rs — In revoke_owned_login_locked, when parse_auth_file fails, return an error or at least tracing::warn before removing the local file, so the user is informed that remote revocation could not be performed.

Assessment

Well-implemented feature with careful redaction and separation of owned vs external credentials. Needs fixes for state cleanup after logout/revoke and remote revocation of superseded tokens before merge; live testing recommended.


Advisory review by Codewhale (codewhale review --pr 5784 --post, head b3d64ff3527f4e8ebbc8e4d38197cea9666fc08a). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale comment

Not approving: Cursor Bugbot was present on this head but completed as skipped (usage limit / incomplete analysis), so the required automated-review signal is not clean. Human review is needed; reviewers were assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@codewhale-agent codewhale-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Codewhale review

PR #5784 adds a native ChatGPT PKCE OAuth login for the openai-codex provider. The implementation is substantial and includes credential storage, refresh, and revoke logic, plus TUI/CLI wiring, documentation, and translations. Overall the approach is sound and reuses existing credential-store patterns, but there are a few security/behavioral edge cases and missing tests worth addressing before merge.

Findings

  • [WARNING] chatgpt-revoke may leave orphaned credential files (crates/tui/src/chatgpt_oauth.rs)
    revoke_owned_login_locked removes only the generation referenced by the config (or the legacy file if no generation exists). If a previous activation's cleanup failed, or another chatgpt-auth-*.json file exists, it remains on disk after 'codewhale auth chatgpt-revoke' succeeds. Logout clears everything via clear_all_chatgpt_oauth_credentials, but the revoke command should arguably also remove all Codewhale-owned ChatGPT token files to honor the user's intent to revoke.
  • [INFO] credentials_valid performs file I/O on hot config checks (crates/tui/src/config.rs)
    active_provider_has_config_api_key now calls crate::chatgpt_oauth::credentials_valid(config) each time. This function reads and parses the credential file from disk. If this check runs during rendering or before each request, it could add latency. Consider caching the validity result or deferring the file read until credentials are actually needed.
  • [INFO] Auth status may report ChatGPT sign-in even when the credential file is missing (crates/cli/src/lib.rs)
    auth_status_lines_for_provider_with_runtime checks only that oauth_credential_generation is a valid ChatGPT generation string, not that the corresponding file exists. If the file is deleted out-of-band, status shows 'codewhale-owned ChatGPT sign-in (availability not probed)', which is misleading because the actual credential resolution will fall back to external consent or fail.
  • [INFO] No test covers the full PKCE flow with real loopback binding (crates/tui/src/chatgpt_oauth.rs)
    Unit tests exercise callback parsing, token exchange, persist/refresh/revoke, and the callback server, but there is no integration test that binds actual loopback ports, starts a mock issuer, and runs pkce_login end-to-end. The PR checklist also notes that live PKCE was not tested against OpenAI. Adding a test that uses ephemeral ports and a local HTTP server would improve confidence.

Suggestions

  • crates/tui/src/chatgpt_oauth.rs — In revoke_owned_login_locked, instead of removing only the current generation or legacy file, enumerate all chatgpt-auth-*.json files (using the existing chatgpt_auth_names_in_store helper) and revoke/remove each one. This ensures no refresh tokens remain on disk after a user-initiated revoke.

Assessment

The feature is well-structured and reuses existing patterns, but the revoke behavior and some status/performance edge cases should be reviewed before merge. The missing end-to-end PKCE test is a notable gap for a security-critical path.


Advisory review by Codewhale (codewhale review --pr 5784 --post, head 9749e41081ddd836fbe10939ca216c33df8ed67d). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.

@cursor

cursor Bot commented Sep 1, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_a5175313-76c0-47c2-a286-3494ef05d371)

@claude

claude Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 1m 36s —— View job


Reviewing PR #5784 — native ChatGPT PKCE sign-in (re-review after latest push)

Todo list

  • Gather context (diff vs origin/main, prior review threads, CLAUDE.md/AGENTS.md rules)
  • Determine what changed since the last review round (previous head 9749e41)
  • Check whether prior findings are resolved
  • Fresh pass for new issues introduced by this push
  • Post final review

What changed since the last review

Two commits landed on top of 9749e41:

  1. a65942bstyle: rustfmt the automation run-file test (a separate, no-parent-in-this-clone commit — effectively the origin/main tip being merged in; message: "automation_manager.rs's json-run-file test predates the rustfmt line-width change... Format only; no behavior.")
  2. 02e4122 — the merge commit joining this branch (9749e41) with that origin/main tip.

I verified this is infrastructure-only for this PR: git diff origin/main...HEAD --stat still shows the same 38 files / +2388/-67 as the PR description, and git diff 9749e41..HEAD -- <every ChatGPT-owned file> (chatgpt_oauth.rs, commands/groups/config/mod.rs, crates/cli/src/lib.rs, crates/config/src/xai_credentials.rs, crates/tui/src/config.rs, crates/tui/src/tui/provider_picker.rs, etc.) returns empty — byte-for-byte unchanged. The 83-file / ~6900-line diff between 9749e41 and HEAD is entirely unrelated origin/main churn merged in: a new "Operate" TUI mode/board (crates/tui/src/operate.rs, automation_manager.rs, automation_panel.rs), MCP OAuth work (crates/tui/src/mcp/oauth.rs), a ReasoningEffort::cycle_next_for_providercycle_next_in refactor, and a launch_screen config-row retirement — none of it touches the ChatGPT PKCE surface. I confirmed this file-by-file (e.g. crates/tui/src/tui/ui/apply.rs and event_loop.rs diffs are all "Operate" board wiring; localization.rs/en.json diffs are all Automation*/Operate* message ids; lib.rs only adds mod operate; and renames a doc comment).

Status of previously flagged findings — unaffected, still open

Verified directly against current HEAD (e.g. crates/tui/src/commands/groups/config/mod.rs:191-197 still reads exactly as before). Since no commits touched the feature files, every finding raised across the prior review rounds on this PR remains open:

  • /auth chatgpt-revoke still blocks the TUI event loop, targets the wrong config, and doesn't update live statecrates/tui/src/commands/groups/config/mod.rs:191-197 calls chatgpt_oauth::revoke_owned_login(None, None) synchronously from the async command dispatcher (blocking ~20s reqwest POST per stored token while holding the OAuth lifecycle lock), with None for config_path (misses the session's active app.config_path) and None for live_config (leaves App.config's in-memory oauth_credential_generation/auth_mode stale). This is the item most reviewers agree should block merge.
    Fix this →
  • auth_mode never reset alongside the generation pointer on revoke/logout (crates/tui/src/config.rs, crates/cli/src/lib.rs) — leaves auth_mode = "oauth" after tokens are gone.
  • Revoke leaves orphaned credential files and silently swallows local-delete errors (crates/tui/src/chatgpt_oauth.rs) — only the currently-referenced generation is removed (not all chatgpt-auth-*.json files), refresh_token.or(access_token) leaves one token unrevoked remotely when both exist, and let _ = store.remove(&name) hides deletion failures behind a reported success.
  • Opaque access tokens without expires_in/JWT exp are treated as always-expired, forcing an avoidable refresh that fails closed when no refresh token happens to be stored.
  • Partial-bind loopback risk: bind_loopback_callback() accepts a port once at least one of IPv4/IPv6 binds; if only one family binds (the other already held by an unrelated local process), the OAuth code could still be delivered to the wrong process instead of failing over to the next port.
  • Unauthenticated loopback connections can abort a pending login before the real redirect arrives (wait_for_callback returns on the first accepted connection within the 300s window), and callback error_description is surfaced to the user without sanitization.
  • Minor/dead-code: credentials_present/credentials_valid are identical; AuthCommand::Chatgpt/ChatgptRevoke arms inside run_auth_command_with_secrets_and_runtime are unreachable (already handled earlier in run()); unused localization key ProviderExternalHintChatgptReview; select_entry hardcodes preference for the built-in client id rather than the configured CODEWHALE_CHATGPT_OAUTH_CLIENT_ID.

None of these are new — they've been raised consistently across the claude, codewhale-agent, Copilot, and Cursor security review rounds on this PR and remain unaddressed since the feature files haven't changed since 9749e41.

Design that still holds up well

Core PKCE mechanics (S256 challenge/verifier, state checked on both success and error callbacks, redacted Debug impls throughout, fail-closed handling of invalid_grant/refresh_token_reused/refresh_token_expired/refresh_token_invalidated/401, honest originator=codewhale, dual-stack loopback bind for the common case, and the multi-chunk callback-read fix from 9749e41) remain solid and unchanged.

Assessment

This push is origin/main integration only (a new "Operate" mode, automation panel, MCP OAuth, and an unrelated rustfmt fix) — none of it touches the ChatGPT OAuth feature code, so there's nothing new to review on this surface. The /auth chatgpt-revoke blocking + wrong-config-file + stale-in-memory-state issue remains the item most reviewers agree should block merge; the rest are correctness/robustness follow-ups already tracked across prior rounds.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale comment

Not approving: Cursor Bugbot was present on this head but completed as skipped (usage limit / incomplete analysis), so the required automated-review signal is not clean. Human review is needed; reviewers were assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale comment

Agentic security review of native ChatGPT PKCE sign-in found two medium issues: loopback callback abort via the first TCP connection, and /auth chatgpt-revoke failing open against a non-default TUI config.

Open in Web View Automation 

Sent by Cursor Security Agent: Security Reviewer

for listener in listeners {
match listener.accept() {
Ok((stream, _)) => {
return handle_callback_stream(stream, expected_state);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Agentic Security Review
Severity: MEDIUM

wait_for_callback() returns on the first accepted loopback connection. Any GET that fails path/query/state checks, or an error= callback that omits state, then fails the entire ChatGPT sign-in. Ports 1455/1457 are well-known and the wait lasts up to 300s, so another local tab, process, or a page that navigates the browser to the loopback callback can abort login before the real redirect arrives.

accept_callback() also skips the state check when an error callback has no state, and interpolates error_description into the user-visible failure shown in the TUI. Success still requires matching state and PKCE, so this is login abort plus attacker-controlled error copy, not token theft.

Impact: During native ChatGPT PKCE sign-in, an unauthenticated request to the loopback callback can cancel the pending login and surface untrusted error text in the TUI.

Fix in Cursor Fix in Web

Reviewed by Cursor Security Reviewer for commit 02e4122. Configure here.

Comment thread crates/tui/src/commands/groups/config/mod.rs Outdated
… live route

Review findings on #5784 that reviewers agreed should block merge:

- The slash command called `revoke_owned_login(None, None)` inline from the
  async command dispatcher: one blocking reqwest POST per stored token under
  the OAuth lifecycle lock froze the TUI, `None` missed the session's own
  `app.config_path`, and `None` left `App.config`'s `oauth_credential_generation`
  and `auth_mode` stale. It now returns `AppAction::StartChatgptRevoke`; the
  loop runs the revoke on `spawn_blocking` against the session config path and
  clears the live route on success, mirroring the PKCE login path.
- `auth_mode = "oauth"` is now reset with the generation pointer on revoke
  (persisted document and live config) and on CLI logout, so a revoked route
  does not keep claiming OAuth.

Evidence: `cargo test -p codewhale-tui --lib chatgpt`
  test result: ok. 14 passed; 0 failed; 0 ignored (includes the new
  chatgpt_revoke_slash_command_defers_to_the_event_loop)
`cargo check -p codewhale-cli` clean; `cargo fmt --all -- --check` clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdbuqwHAXSDcikPiS6L6Qw
@cursor

cursor Bot commented Sep 1, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_c202dced-8c67-4baa-84a3-2f517ab781cb)

@claude

claude Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 1m 29s —— View job


Reviewing PR #5784 — native ChatGPT PKCE sign-in (re-review after latest push)

Todo list

  • Gather context (diff vs origin/main, prior review threads, CLAUDE.md/AGENTS.md rules)
  • Determine what changed since the last review round (previous head 02e4122, new head 0198746)
  • Check whether the long-standing /auth chatgpt-revoke finding is resolved
  • Fresh pass for new issues introduced by this push
  • Post final review

What changed since the last review

One commit landed on top of 02e4122: 0198746fix(tui): /auth chatgpt-revoke runs off the event loop and clears the live route. It touches crates/cli/src/lib.rs, crates/tui/src/chatgpt_oauth.rs, crates/tui/src/commands/groups/config/mod.rs, crates/tui/src/commands/mod.rs, crates/tui/src/config.rs, crates/tui/src/tui/app/types.rs, crates/tui/src/tui/ui/apply.rs.

This resolves the item every reviewer agreed should block merge

Verified directly against current HEAD:

  • No longer blocks the event loop. crates/tui/src/commands/groups/config/mod.rs:191-192 now returns CommandResult::action(AppAction::StartChatgptRevoke) instead of calling revoke_owned_login inline. The new run_chatgpt_revoke_from_tui (crates/tui/src/tui/ui/apply.rs:3048-3072) runs the revoke via tokio::task::spawn_blocking, mirroring run_chatgpt_pkce_login_from_tui.
  • Targets the session's own config. The closure captures app.config_path.clone() and passes config_path.as_deref() into revoke_owned_login, instead of the old hardcoded None that always resolved the default config path.
  • Live in-memory state is no longer stale. On success, run_chatgpt_revoke_from_tui calls config.clear_codewhale_owned_chatgpt_oauth() after the blocking task completes (done on the async side since Config isn't moved into the blocking closure) — this now clears both oauth_credential_generation and auth_mode (see next point), and a status message/history cell is posted either way.
  • auth_mode is reset alongside the generation pointer, closing a gap flagged in three separate review rounds:
    • Persisted document: revoke_owned_login_locked (chatgpt_oauth.rs:854-867) now also unsets providers.openai_codex.auth_mode in the TOML document when it was "oauth".
    • Live config: Config::clear_codewhale_owned_chatgpt_oauth (crates/tui/src/config.rs:5745-5757) now clears entry.auth_mode alongside the generation.
    • CLI logout: run_logout_command_with_secrets_unlocked (crates/cli/src/lib.rs:2434-2439) mirrors the same reset.
  • New regression test: chatgpt_revoke_slash_command_defers_to_the_event_loop (crates/tui/src/commands/mod.rs:674-682) asserts /auth chatgpt-revoke returns AppAction::StartChatgptRevoke rather than executing inline — pins exactly the behavior this commit changes. AppAction is already imported in that test module, so it should compile cleanly.

I was not able to run cargo test/cargo check myself in this sandboxed environment (command execution requires approval that isn't available here), so this is verified by reading the code paths rather than by re-running the suite; the commit message's own evidence (14 passed; 0 failed, cargo check -p codewhale-cli clean, cargo fmt --all -- --check clean) is consistent with what the diff shows.

Remaining open items — unaffected by this push, still lower-severity follow-ups

None of these were in scope for this commit and none are newly introduced by it:

  • Local credential removal errors during revoke are still silently swallowedchatgpt_oauth.rs: let _ = store.remove(&name);.
  • Revoke only removes the currently-referenced generation, not all chatgpt-auth-*.json files — orphaned files from earlier activations aren't cleaned up by /auth chatgpt-revoke.
  • Revoke only revokes one token per entry (refresh_token.or(access_token)), leaving the other valid server-side when both exist.
  • Opaque access tokens without expires_in/JWT exp are treated as always-expired, forcing an avoidable refresh that fails closed when no refresh token is stored.
  • Partial-bind loopback risk: bind_loopback_callback() accepts a port once at least one of IPv4/IPv6 binds.
  • Unauthenticated loopback connections can abort a pending login before the real redirect arrives; callback error_description is surfaced to the user without sanitization.
  • Minor/dead-code: credentials_present/credentials_valid are identical; unreachable AuthCommand::Chatgpt/ChatgptRevoke match arms in run_auth_command_with_secrets_and_runtime; unused localization key ProviderExternalHintChatgptReview (confirmed still declared/translated but never rendered in provider_picker.rs).

None of these individually block merge on their own — they're correctness/robustness gaps already tracked across prior rounds, mostly lower severity than the fixed issue (UI freeze + wrong-file + stale-state on a credential-revocation path).

Assessment

This push directly and correctly addresses the one finding that recurred across every review round on this PR and that reviewers agreed should block merge: /auth chatgpt-revoke no longer blocks the TUI event loop, now targets the session's actual config file, and now clears both the generation pointer and auth_mode from persisted and live state (extended consistently to CLI logout too). The fix is well-scoped, matches the existing PKCE-login pattern (spawn_blocking + session config path), and ships with a targeted regression test. I found no new issues introduced by this commit. The remaining open items are pre-existing, lower-severity robustness/cleanup gaps that can be tracked as follow-ups rather than merge blockers.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale comment

Not approving: Cursor Bugbot was present on this head but completed as skipped (usage limit / incomplete analysis), so the required automated-review signal is not clean. Human review is needed; reviewers were assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale comment

Agentic security review of native ChatGPT PKCE sign-in: one new medium finding on incomplete revoke of leftover Codewhale-owned credential generations. An earlier loopback callback abort finding is still present and was not re-posted.

Open in Web View Automation 

Sent by Cursor Security Agent: Security Reviewer

if let Some(config) = live_config {
config.clear_codewhale_owned_chatgpt_oauth();
}
let names = match previous.as_deref() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Agentic Security Review
Severity: MEDIUM

revoke_owned_login only remotely revokes and deletes the single oauth_credential_generation basename (or, if that pointer is missing/invalid, only the legacy chatgpt-oauth.json). Login already treats superseded-generation unlink as best-effort, and store.remove errors are swallowed here, so a prior chatgpt-auth-<hex>.json can keep a still-valid refresh token.

After /auth chatgpt-revoke / codewhale auth chatgpt-revoke, the config pointer is gone so the session looks signed out, while leftover files in $CODEWHALE_HOME/credentials can still mint access tokens. Logout already sweeps every ChatGPT generation via clear_all_chatgpt_oauth_credentials() / clear_chatgpt.

Impact: A user who revoked to invalidate ChatGPT credentials can leave live refresh tokens on disk that OpenAI will still honor.

Fix in Cursor Fix in Web

Reviewed by Cursor Security Reviewer for commit 0198746. Configure here.

@codewhale-agent codewhale-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Codewhale review

Adds native ChatGPT PKCE sign-in for openai-codex, storing refreshable tokens in Codewhale-owned credentials, with CLI/TUI wiring, config-layer support, locale strings, and documentation. The implementation is generally robust with redaction, state checks, loopback binding, and unit tests, but has concerns around blocking I/O in async contexts, the hardcoded public client ID / revoke endpoint, and legal ambiguity.

Findings

  • [WARNING] Blocking OAuth refresh may stall the async event loop (crates/tui/src/chatgpt_oauth.rs)
    get_owned_credentials uses ReqwestTokenClient (a blocking reqwest client) and is invoked from Config::codex_credentials during credential resolution. If this resolution happens on the Tokio async runtime, the blocking refresh can block a worker thread and potentially starve other tasks. Consider using an async client or wrapping the refresh in spawn_blocking.
  • [WARNING] Hardcoded public Codex CLI client ID and ambiguous terms (crates/tui/src/chatgpt_oauth.rs)
    The code hardcodes app_EMoamEEZ73f0CkXaXp7hrann with originator=codewhale. OpenAI has not published a third-party client-registration path for this public Codex client, and the PR description itself labels this as 'ambiguous'. This introduces legal/ToS risk and could break if OpenAI revokes the client ID or enforces originator restrictions.
  • [WARNING] Remote revoke endpoint is hardcoded despite discovery advertising revocation_endpoint (crates/tui/src/chatgpt_oauth.rs)
    The PR summary and docs mention using the discovery revocation_endpoint, but the implementation hardcodes {issuer}/api/accounts/oauth/revoke. While the comment explains the rationale (cleanup should not depend on discovery), this will silently fail if OpenAI changes the path, and it contradicts the stated design. Consider making the endpoint configurable or using discovery with a fallback.
  • [INFO] Missing test for remote revoke failure still clearing local credentials (crates/tui/src/chatgpt_oauth.rs)
    The tests cover a successful remote revoke, but there is no test verifying that local credential files are removed even when the remote revoke returns an error. The code appears to handle this correctly, but a regression test would strengthen the guarantee.
  • [INFO] Synchronous revoke in CLI blocks the async runtime (crates/tui/src/lib.rs)
    run_chatgpt_pkce_revoke is a synchronous function that performs blocking network calls via revoke_owned_login, and it is called directly from the async run_async_main_dispatch. This can block the async event loop. The TUI path correctly uses spawn_blocking; the CLI path should follow the same pattern.

Assessment

The PR is well-structured with careful error handling, redaction, and test coverage. However, the blocking I/O in async paths, the hardcoded public client ID and revoke endpoint, and the legal ambiguity should be addressed before merging. The missing test for revoke failure cleanup is minor but valuable.


Advisory review by Codewhale (codewhale review --pr 5784 --post, head 019874602d922869f545085314c16941f00388d8). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.

@cursor

cursor Bot commented Sep 1, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_5cd9d01e-bed1-4436-bb00-b64eb0e78584)

@claude

claude Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 38s —— View job


Reviewing PR #5784 — native ChatGPT PKCE sign-in (re-review after latest push)

Todo list

  • Gather context (diff vs origin/main, prior review threads, CLAUDE.md/AGENTS.md rules)
  • Determine what changed since the last review round (previous head 0198746)
  • Check whether the fix for /auth chatgpt-revoke is still intact
  • Fresh pass for new issues introduced by this push
  • Post final review

What changed since the last review

Nothing. The only commit since 0198746 is 62cce8fci: re-run — Buildkite build 1502 hung at started for 55 minutes, an empty commit (git diff 0198746..62cce8f --stat returns no files) used purely to retrigger a hung Buildkite build. No source changed.

Status of the last fix — confirmed still in place

The prior review round's fix for the item every reviewer agreed should block merge (/auth chatgpt-revoke blocking the TUI event loop, targeting the wrong config, and leaving live state stale) is unchanged and intact at current HEAD:

  • crates/tui/src/commands/groups/config/mod.rs:191-192 still returns CommandResult::action(AppAction::StartChatgptRevoke) instead of calling revoke_owned_login inline.
  • run_chatgpt_revoke_from_tui (crates/tui/src/tui/ui/apply.rs) still runs the revoke via tokio::task::spawn_blocking, using the session's own app.config_path, and clears both oauth_credential_generation and auth_mode from live and persisted state on success.

Remaining open items — unaffected by this push, unchanged from prior rounds

These are pre-existing, lower-severity robustness/cleanup gaps already tracked across many review rounds on this PR, none touched by 62cce8f:

  • Revoke only removes the currently-referenced credential generation, not all chatgpt-auth-*.json files, and only revokes one token per entry (refresh_token.or(access_token)) — flagged again in the latest Cursor agentic security pass at crates/tui/src/chatgpt_oauth.rs:873.
  • Local credential removal errors during revoke are silently swallowed (let _ = store.remove(&name);).
  • Opaque access tokens without expires_in/JWT exp are treated as always-expired, forcing an avoidable refresh that fails closed when no refresh token is stored.
  • Partial-bind loopback risk: bind_loopback_callback() accepts a port once at least one of IPv4/IPv6 binds.
  • Unauthenticated loopback connections can abort a pending login before the real redirect arrives (wait_for_callback), and callback error_description is surfaced to the user without sanitization — reconfirmed by Cursor's latest security pass at chatgpt_oauth.rs:481.
  • Minor/dead-code: credentials_present/credentials_valid are identical; unreachable AuthCommand::Chatgpt/ChatgptRevoke match arms in run_auth_command_with_secrets_and_runtime; unused localization key ProviderExternalHintChatgptReview; select_entry hardcodes preference for the built-in client id.

Assessment

This push is CI infrastructure only (an empty commit to retrigger a hung Buildkite build) — it does not touch the ChatGPT OAuth feature in any way. No new findings. The core PKCE design and the previously-blocking /auth chatgpt-revoke fix both remain solid; the items listed above are follow-up robustness gaps, not new regressions, and have been raised consistently across prior rounds without changing in severity.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale comment

Not approving: Cursor Bugbot was present on this head but completed as skipped (usage limit / incomplete analysis), so the required automated-review signal is not clean. Human review is needed; reviewers were assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@codewhale-agent codewhale-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Codewhale review

Adds native ChatGPT PKCE sign-in for the openai-codex provider, with browser loopback callback, Codewhale-owned token storage, refresh/revoke support, and provider picker update. The code is generally well-structured, security-conscious (redacted Debug, state validation, honest originator), and has substantial unit tests.

Findings

  • [WARNING] Silent fallback to production issuer hides invalid CODEWHALE_CHATGPT_OAUTH_ISSUER (crates/tui/src/chatgpt_oauth.rs:172)
    In build_authorize_url, Url::parse of the configured issuer falls back to the hardcoded https://auth.openai.com/oauth/authorize on any parse error. If a user sets CODEWHALE_CHATGPT_OAUTH_ISSUER to a malformed or test issuer, the code silently redirects them to the production authorization endpoint instead of surfacing the misconfiguration. Prefer propagating the parse error so custom issuer mistakes fail loudly.
  • [INFO] Unused localization key ProviderExternalHintChatgptReview (crates/tui/src/localization.rs:1674)
    The new MessageId::ProviderExternalHintChatgptReview and its locale strings are never referenced in Rust code. The picker footer uses MessageId::ProviderExternalActionReuseCodex for the 'E' action hint, so the new review hint string is dead. Either remove the unused key from the enum and locale files, or wire it into the render path where it was intended.
  • [INFO] Standalone auth chatgpt-revoke blocks the async main thread (crates/tui/src/lib.rs:8163)
    run_chatgpt_pkce_revoke is a synchronous function that calls chatgpt_oauth::revoke_owned_login, which performs a blocking HTTP round trip under the OAuth lifecycle lock. In the async CLI entrypoint this blocks the executor thread. The TUI event-loop path avoids this by using spawn_blocking, but the standalone codewhale-tui auth chatgpt-revoke command still blocks. Consider wrapping this standalone revoke in spawn_blocking as well, or making the function async and awaiting a blocking task.

Assessment

The PR is well-tested and carefully implements the documented PKCE flow with fail-closed token refresh and local-first revoke. The issues found are minor and do not block merge, but the silent issuer fallback should be addressed to avoid surprising production calls during custom issuer testing.


Advisory review by Codewhale (codewhale review --pr 5784 --post, head 62cce8ff0c15dc59e9a52faf4f440b3dfba03d06). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.

let response = client
.post(url)
.form(form)
.send()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[WARNING] Silent fallback to production issuer hides invalid CODEWHALE_CHATGPT_OAUTH_ISSUER

In build_authorize_url, Url::parse of the configured issuer falls back to the hardcoded https://auth.openai.com/oauth/authorize on any parse error. If a user sets CODEWHALE_CHATGPT_OAUTH_ISSUER to a malformed or test issuer, the code silently redirects them to the production authorization endpoint instead of surfacing the misconfiguration. Prefer propagating the parse error so custom issuer mistakes fail loudly.

ChatgptAuthChoiceIntro,
ChatgptAuthChoicePkceOption,
ChatgptAuthChoiceImportOption,
ProviderExternalHintChatgptReview,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[INFO] Unused localization key ProviderExternalHintChatgptReview

The new MessageId::ProviderExternalHintChatgptReview and its locale strings are never referenced in Rust code. The picker footer uses MessageId::ProviderExternalActionReuseCodex for the 'E' action hint, so the new review hint string is dead. Either remove the unused key from the enum and locale files, or wire it into the render path where it was intended.

Comment thread crates/tui/src/lib.rs

fn run_chatgpt_pkce_revoke(config_path: Option<&Path>) -> Result<()> {
chatgpt_oauth::revoke_owned_login(config_path, None)?;
println!("Revoked Codewhale-owned ChatGPT tokens. Codex CLI consent is unchanged.");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[INFO] Standalone auth chatgpt-revoke blocks the async main thread

run_chatgpt_pkce_revoke is a synchronous function that calls chatgpt_oauth::revoke_owned_login, which performs a blocking HTTP round trip under the OAuth lifecycle lock. In the async CLI entrypoint this blocks the executor thread. The TUI event-loop path avoids this by using spawn_blocking, but the standalone codewhale-tui auth chatgpt-revoke command still blocks. Consider wrapping this standalone revoke in spawn_blocking as well, or making the function async and awaiting a blocking task.

run_logout_command_with_secrets wraps everything in
with_xai_oauth_revocation_transaction, which holds the non-reentrant
lifecycle mutex; the new chatgpt clear inside it called
clear_all_chatgpt_oauth_credentials, which takes the same mutex again.
Every logout hung forever — the three logout_* tests spun >47m on
buildkite and both Actions test jobs stuck in_progress for 75+ minutes,
and a real 'codewhale auth logout' would hang the same way.

Add clear_all_chatgpt_oauth_credentials_locked() for callers already
inside the transaction (store open is lock-free; the fd lock is not
re-taken) and use it from logout.

Evidence: cargo test -p codewhale-cli --lib logout_
'test result: ok. 5 passed; 0 failed' in 0.05s (was: infinite hang).

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@cursor

cursor Bot commented Sep 1, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_31fa8335-0d6f-48dc-8c6e-9fd511393e73)

@claude

claude Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 2m 7s —— View job


Reviewing PR #5784 — native ChatGPT PKCE sign-in (re-review after latest push)

Todo list

  • Gather context (diff vs origin/main, prior review threads, CLAUDE.md/AGENTS.md rules)
  • Determine what changed since the last review round (previous head 62cce8f)
  • Check whether the /auth chatgpt-revoke / lifecycle-lock findings are affected
  • Fresh pass for new issues introduced by this push
  • Post final review

What changed since the last review

One commit landed on top of 62cce8f: f59eb47fix(cli): logout deadlocks re-entering the OAuth lifecycle lock. It touches crates/cli/src/lib.rs, crates/config/src/lib.rs, crates/config/src/xai_credentials.rs (+18/-4 total).

The fix is correct

run_logout_command_with_secrets wraps the whole logout in with_xai_oauth_revocation_transaction, which acquires the process-wide PROCESS_LOCK: OnceLock<Mutex<()>> inside with_xai_oauth_lifecycle_lock (crates/config/src/xai_credentials.rs:160-177) plus the cross-process fd lock. run_logout_command_with_secrets_unlocked previously called codewhale_config::clear_all_chatgpt_oauth_credentials() (crates/cli/src/lib.rs:2447, pre-fix), which itself calls with_xai_oauth_lifecycle_lock again — a second PROCESS_LOCK.lock() on the same thread. std::sync::Mutex is not reentrant, so this is a guaranteed self-deadlock on every logout, independent of whether any ChatGPT credentials actually exist (the call is unconditional in the logout path). That matches the commit's evidence: 47+ minute CI hangs on the logout_* tests and two stuck Actions jobs.

The fix adds clear_all_chatgpt_oauth_credentials_locked() (crates/config/src/xai_credentials.rs:1699-1702), which calls XaiOAuthCredentialStore::open() directly instead of going back through with_xai_oauth_lifecycle_lock. I verified open() (crates/config/src/xai_credentials.rs:208-211open_owned_credentials_directory) only does directory/fd setup via raw libc/Windows calls — it never touches PROCESS_LOCK or the .xai-oauth.lock fd lock — so the doc comment's claim ("store open is lock-free; the fd lock is not re-taken") holds. crates/cli/src/lib.rs:2449 now calls the _locked variant with a comment explaining why, and crates/config/src/lib.rs's re-export list adds clear_all_chatgpt_oauth_credentials_locked alongside the existing (still-used-elsewhere) clear_all_chatgpt_oauth_credentials.

I checked for other latent instances of this same bug class — anything invoking clear_all_chatgpt_oauth_credentials() (the locking variant) from inside an already-held with_xai_oauth_lifecycle_lock/with_xai_oauth_revocation_transaction closure — across crates/tui/src/chatgpt_oauth.rs, crates/tui/src/xai_oauth.rs, and crates/tui/src/config.rs (save_api_key_for_identity, clear_api_key, clear_active_provider_api_key, all of which wrap in with_xai_oauth_revocation_transaction for the Xai provider). None of those closures call the locking chatgpt-clear function, so this looks like the only site with the bug, and the new _locked() function has exactly one caller (grep confirms), used correctly.

I could not run cargo test -p codewhale-cli --lib logout_ myself in this sandboxed environment (cargo commands require approval that isn't available here), so this is verified by reading the lock implementation and call graph rather than re-executing the suite. One minor note: the commit message cites "5 passed" but only 3 test fns match logout_ in codewhale-cli's lib target (logout_removes_plaintext_provider_keys, logout_clears_keyring_credentials_for_all_providers, logout_clears_account_session_and_daytona_slot); the other 2 matches are in codewhale-config (xai_credentials.rs), a different package than -p codewhale-cli selects. Cosmetic discrepancy in the commit message, not a code issue — all three codewhale-cli tests do exercise run_logout_command_with_secrets, so they do cover the previously-deadlocking path.

Status of previously flagged findings — unaffected by this push

This commit is scoped entirely to the logout deadlock; it doesn't touch chatgpt_oauth.rs's revoke/refresh/loopback-bind logic or the TUI /auth chatgpt-revoke command path. Everything raised in earlier rounds (now fixed: blocking-revoke/wrong-config/stale-state, auth_mode reset on revoke/logout) remains as previously assessed; the still-open lower-severity items are unchanged: orphaned credential-file cleanup on revoke, single-token remote revoke (refresh_token.or(access_token)), opaque-token expiry treated as always-expired, partial-bind loopback risk, unauthenticated-loopback-abort of a pending login, and the unused ProviderExternalHintChatgptReview localization key.

Assessment

This is a correct, well-isolated, well-evidenced fix for a real and severe bug (every codewhale auth logout / CLI logout call would hang forever). The root cause diagnosis (non-reentrant std::sync::Mutex re-entered via a nested with_xai_oauth_lifecycle_lock call) is accurate, the fix follows the existing "callers already inside the transaction use a _locked variant" pattern used elsewhere in this file, and I found no other call site with the same bug. No new issues introduced by this push.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Not approving: Cursor Bugbot was present on this head but completed as skipped (usage limit / incomplete analysis), so the required automated-review signal is not clean. Human review is needed; reviewers were assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Agentic security review found one new medium issue: TUI logout does not destroy Codewhale-owned ChatGPT PKCE tokens, unlike CLI codewhale logout. Prior loopback-callback abort and incomplete chatgpt-revoke file sweep findings remain open and unacknowledged.

Open in Web View Automation 

Sent by Cursor Security Agent: Security Reviewer

Comment thread crates/tui/src/config.rs
entry.external_credentials = None;
}

pub(crate) fn clear_codewhale_owned_chatgpt_oauth(&mut self) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Agentic Security Review
Severity: MEDIUM

TUI /logout and codewhale-tui --logout never destroy Codewhale-owned ChatGPT PKCE tokens. CLI codewhale logout now unsets the openai-codex generation pointer and sweeps chatgpt-auth-*.json, but TUI logout still only special-cases xAI OAuth. This helper exists for in-memory revoke cleanup and is unused by those logout paths, so the UI can report credentials cleared while codex_credentials() still mints access tokens from leftover files.

Impact: After native ChatGPT sign-in, TUI logout leaves a live refresh token on disk and the next Codex request continues as signed in.

Fix in Cursor Fix in Web

Reviewed by Cursor Security Reviewer for commit f59eb47. Configure here.

@Hmbown
Hmbown merged commit e6456fa into main Sep 1, 2026
31 of 33 checks passed
@Hmbown
Hmbown deleted the feat/native-chatgpt-pkce-5778-20260831 branch September 1, 2026 22:07

@codewhale-agent codewhale-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Codewhale review

Adds native ChatGPT PKCE sign-in using the published OpenAI issuer and Codewhale-owned token storage. The core adapter is thorough and has good unit coverage, but several security and robustness concerns remain around loopback binding, revocation error handling, and blocking work on the async runtime.

Findings

  • [WARNING] Loopback callback may bind only one address family (crates/tui/src/chatgpt_oauth.rs)
    bind_loopback_callback returns success as soon as at least one listener is bound. The redirect URI is http://localhost, which can resolve to ::1 on IPv6-first hosts. If Codewhale binds only 127.0.0.1 because another process already owns [::1]:1455, the browser can deliver the authorization code to that other local process instead of Codewhale. Distinguish AddrInUse/Access on the alternate family from AddrNotAvailable/unsupported and fail closed, or bind both stacks atomically before opening the browser.
  • [WARNING] Revoke ignores local credential deletion failures (crates/tui/src/chatgpt_oauth.rs)
    In revoke_owned_login_locked, each store.remove(&name) result is discarded with let _ =. If removal fails due to permissions, directory handle failure, or other I/O errors, the function still returns Ok(()), so the TUI/CLI reports success while valid tokens remain on disk. At minimum log a warning on removal error, and consider propagating the first failure so callers do not claim revocation succeeded.
  • [WARNING] ChatGPT revoke blocks the async runtime (crates/tui/src/lib.rs)
    crates/tui/src/lib.rs calls run_chatgpt_pkce_revoke directly in run_async_main_dispatch. That function performs at least one blocking HTTP round trip under the lifecycle lock, tying up a Tokio worker thread in CLI/TUI dispatchers. The interactive run_chatgpt_revoke_from_tui path already uses tokio::task::spawn_blocking; the CLI/headless path should do the same.
  • [INFO] Unused localization key ProviderExternalHintChatgptReview (crates/tui/src/localization.rs:1670)
    MessageId ProviderExternalHintChatgptReview and its translations are added in this PR but never referenced by any code path. The TUI footer uses ProviderExternalActionReuseCodex instead. This adds dead locale keys and can confuse future translators; either remove the unused key or use it in the appropriate hint.
  • [INFO] Missing test update for setup readiness copy (crates/tui/src/tui/setup/mod.rs:355)
    The user-facing message in crates/tui/src/tui/setup/mod.rs changed from run codex login... to Sign in with ChatGPT..., but no corresponding test is updated in the diff. Existing tui/setup readiness tests may assert the old string and fail under a full workspace test run. Confirm and update tests in that module before merge.
  • [INFO] Refresh network I/O holds global OAuth lifecycle lock (crates/tui/src/chatgpt_oauth.rs)
    get_owned_credentials_with acquires with_xai_oauth_lifecycle_lock and calls refresh_access_token inside the locked closure. While a refresh is waiting on the network, xAI/ChatGPT login, revoke, and logout operations are blocked. Consider performing the refresh outside the lock and reapplying the result under lock, or narrowing the lock to the shared credentials directory only.

Assessment

The implementation is comprehensive and unit-tested, but it should not merge without addressing the loopback family fallback and revoke deletion error swallowing. The dead localization key and likely missing setup test update also need cleanup, and the CLI revoke path should avoid blocking the async runtime.


Advisory review by Codewhale (codewhale review --pr 5784 --post, head f59eb47b79ca5209f88b6b2ceb63f9e2932af7e7). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.

XaiAuthChoiceIntro,
XaiAuthChoiceApiKeyOption,
XaiAuthChoiceDeviceOAuthOption,
ChatgptAuthChoiceTitle,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[INFO] Unused localization key ProviderExternalHintChatgptReview

MessageId ProviderExternalHintChatgptReview and its translations are added in this PR but never referenced by any code path. The TUI footer uses ProviderExternalActionReuseCodex instead. This adds dead locale keys and can confuse future translators; either remove the unused key or use it in the appropriate hint.

@@ -355,7 +355,7 @@ impl SetupRuntimeFacts {
format!("{}; retry or open /provider", readiness.label())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[INFO] Missing test update for setup readiness copy

The user-facing message in crates/tui/src/tui/setup/mod.rs changed from run codex login... to Sign in with ChatGPT..., but no corresponding test is updated in the diff. Existing tui/setup readiness tests may assert the old string and fail under a full workspace test run. Confirm and update tests in that module before merge.

Hmbown pushed a commit that referenced this pull request Sep 1, 2026
…5797, #5796, #5795, #5805, #5800)

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Hmbown pushed a commit that referenced this pull request Sep 1, 2026
build_authorize_url silently fell back to the production authorize
endpoint when CODEWHALE_CHATGPT_OAUTH_ISSUER failed to parse, sending
the browser to a sign-in the user aimed somewhere else (review WARNING
on #5784's final pass). The env's unset/empty default still resolves the
production issuer; only a parse failure errors, naming the env var.

cargo test -p codewhale-tui --lib -- chatgpt: 15 passed; 0 failed
(includes malformed_issuer_fails_loudly_not_to_production).

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
ysm827 pushed a commit to ysm827/DeepSeek-TUI that referenced this pull request Sep 2, 2026
…utomation slice 1

Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Hunter Bown <hmbown@gmail.com>
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.

Native ChatGPT/Codex subscription sign-in without the Codex CLI installed

3 participants