Files
DeepSeek-TUI/docs
Hunter Bown e6456fa2e3 feat(tui): native ChatGPT PKCE sign-in for openai-codex (#5784)
* feat(tui): native ChatGPT PKCE sign-in for openai-codex (#5778)

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>

* docs(changelog): record ChatGPT PKCE and Pod receipts (#5778, #5776)

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

* fix(tui): accept the ChatGPT callback on either loopback family

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>

* docs(changelog): drop the branch-time entry; entries move to merge-time receipts on main

* fix(test): pin the consent boundary to the ChatGPT auth choice stage

The #5778 auth choice for openai-codex routes plain Enter to
Stage::ChatgptAuthChoice before any key entry. The #5772 regression
test pinned the old Stage::KeyEntry landing and failed on the branch.
Walk the new path (Enter -> choice, 2 -> Import from Codex CLI,
Enter -> ExternalConsentChoice) and keep every no-I/O trap assertion.

* fix(tui): read the ChatGPT OAuth callback until the header terminator

One read() is not one request: a fragmented delivery truncates the
query and parses as a missing state parameter, failing the login.
Read until the blank line ending the HTTP headers (bounded by the
4096-byte buffer and the 5s timeout).

Proven failing first: callback_server_handles_success_and_error_requests
panicked 'OAuth callback missing state' on this branch head (ubuntu CI
run 33490133817 and locally); chatgpt_oauth suite now 11 passed; 0 failed.

* fix(tui): /auth chatgpt-revoke runs off the event loop and clears the 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

* ci: re-run — Buildkite build 1502 hung at started for 55 minutes

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

* fix(cli): logout deadlocks re-entering the OAuth lifecycle lock

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>

---------

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Co-authored-by: CodeWhale Bot <bot@codewhale.net>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-01 15:07:26 -07:00
..