mirror of
https://github.com/openai/codex.git
synced 2026-10-22 17:58:46 +00:00
Fallback login callback port when default is busy (#19334)
## Summary - Keep the preferred ChatGPT login callback port `1455` first. - Preserve the existing `/cancel` recovery for stale Codex login servers. - Fall back to the registered localhost callback port `1457` when `1455` remains unavailable. ## Why Cursor and Codex Desktop both use the ChatGPT account login callback server. On Windows, Cursor can already be listening on `127.0.0.1:1455` / `[::1]:1455`, causing Codex Desktop sign-in to fail with: `Local callback port 1455 is already in use on this machine.` Codex already attempted to cancel a stale Codex login server on that port, but if the listener does not release the port, the old behavior was to fail. The new behavior falls back to `1457`, which matches the fixed redirect URI being registered server-side in `openai/openai#863817`. This keeps the OAuth `redirect_uri` inside Hydra's exact allow-list instead of choosing an arbitrary ephemeral port. ## Validation - `just fmt` - `cargo test -p codex-login` - `git diff --check HEAD~1..HEAD`
This commit is contained in:
@@ -50,6 +50,8 @@ use tracing::warn;
|
||||
|
||||
const DEFAULT_ISSUER: &str = "https://auth.openai.com";
|
||||
const DEFAULT_PORT: u16 = 1455;
|
||||
// Keep in sync with the Codex CLI Hydra redirect URI allow-list.
|
||||
const FALLBACK_PORT: u16 = 1457;
|
||||
static LOGIN_ERROR_PAGE_TEMPLATE: LazyLock<Template> = LazyLock::new(|| {
|
||||
Template::parse(include_str!("assets/error.html"))
|
||||
.unwrap_or_else(|err| panic!("login error page template must parse: {err}"))
|
||||
@@ -527,9 +529,12 @@ fn send_cancel_request(port: u16) -> io::Result<()> {
|
||||
}
|
||||
|
||||
fn bind_server(port: u16) -> io::Result<Server> {
|
||||
let bind_address = format!("127.0.0.1:{port}");
|
||||
let preferred_bind_address = format!("127.0.0.1:{port}");
|
||||
let fallback_bind_address = format!("127.0.0.1:{FALLBACK_PORT}");
|
||||
let mut bind_address = preferred_bind_address.clone();
|
||||
let mut cancel_attempted = false;
|
||||
let mut attempts = 0;
|
||||
let mut using_fallback_port = false;
|
||||
const MAX_ATTEMPTS: u32 = 10;
|
||||
const RETRY_DELAY: Duration = Duration::from_millis(200);
|
||||
|
||||
@@ -543,10 +548,10 @@ fn bind_server(port: u16) -> io::Result<Server> {
|
||||
.map(|io_err| io_err.kind() == io::ErrorKind::AddrInUse)
|
||||
.unwrap_or(false);
|
||||
|
||||
// If the address is in use, there is probably another instance of the login server
|
||||
// running. Attempt to cancel it and retry.
|
||||
// If the address is in use, there may be another instance of the login server
|
||||
// running. Attempt to cancel it and retry before falling back.
|
||||
if is_addr_in_use {
|
||||
if !cancel_attempted {
|
||||
if !cancel_attempted && !using_fallback_port {
|
||||
cancel_attempted = true;
|
||||
if let Err(cancel_err) = send_cancel_request(port) {
|
||||
eprintln!("Failed to cancel previous login server: {cancel_err}");
|
||||
@@ -556,6 +561,18 @@ fn bind_server(port: u16) -> io::Result<Server> {
|
||||
thread::sleep(RETRY_DELAY);
|
||||
|
||||
if attempts >= MAX_ATTEMPTS {
|
||||
if port == DEFAULT_PORT && !using_fallback_port {
|
||||
warn!(
|
||||
%preferred_bind_address,
|
||||
%fallback_bind_address,
|
||||
"default login callback port is unavailable; falling back to the registered fallback port"
|
||||
);
|
||||
bind_address = fallback_bind_address.clone();
|
||||
attempts = 0;
|
||||
using_fallback_port = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::AddrInUse,
|
||||
format!("Port {bind_address} is already in use"),
|
||||
|
||||
Reference in New Issue
Block a user