Skip to content

feat(oauth): retry adjacent ports when OAuth callback port is busy#306

Open
Destynova2 wants to merge 1 commit intomainfrom
feat/oauth-port-retry
Open

feat(oauth): retry adjacent ports when OAuth callback port is busy#306
Destynova2 wants to merge 1 commit intomainfrom
feat/oauth-port-retry

Conversation

@Destynova2
Copy link
Copy Markdown
Contributor

Summary

  • The OAuth callback listener spawned in src/server/lifecycle.rs previously did a single TcpListener::bind on the configured oauth_callback_port (default 1455). When that port was busy, the OAuth flow simply broke — the user had to manually free the port to recover.
  • The bind path now tries the configured port first, then falls back to up to 9 adjacent ports (10 attempts total) via a new crate::shared::net::bind_with_port_retry helper. The actually-bound port is stored in AppState::actual_oauth_callback_port so the OAuth handlers can build a redirect_uri that matches the live listener.
  • A new OAuthConfig::with_callback_port rewrites the localhost callback URL in the configs at src/auth/oauth.rs:161 (OpenAI Codex) and src/auth/oauth.rs:191 (Gemini). Anthropic's remote console.anthropic.com redirect is untouched.
  • Logs make the chosen port explicit:
    • info! when the configured port worked.
    • warn! (OAuth callback port {} busy; bound on 127.0.0.1:{} instead) on fallback.
    • error! (Failed to bind OAuth callback server on 127.0.0.1 in port range 1455..=1464) when every attempt failed.
  • 7 new unit tests (4 in src/shared/net.rs for the retry helper, 3 in src/auth/oauth.rs for with_callback_port) cover: free base port, falling back to next port, zero-attempts rejection, exhausted range error, localhost rewrite for OpenAI Codex, localhost rewrite for Gemini, and remote redirect untouched for Anthropic.

Test plan

  • cargo fmt --check
  • cargo clippy --tests --all-targets -- -D warnings
  • cargo nextest run --lib auth::oauth shared::net — 13/13 pass
  • Full cargo nextest run --lib clean for everything except 4 pre-existing failures already broken on main (missing presets/{medium,local,cheap,fast}.toml referenced by src/preset/mod.rs); those failures are unrelated to this PR.
  • CI green

🤖 Generated with Claude Code

The OAuth callback listener (src/server/lifecycle.rs) used to fail outright
when its configured port (1455 by default, see oauth.rs:161 for OpenAI Codex
and oauth.rs:191 for Gemini) was already in use by another local process,
making the OAuth flow unrecoverable for users with port collisions.

The callback now binds the configured port first and falls back to up to 9
adjacent ports (10 total attempts). The actually-bound port is stored in
AppState::actual_oauth_callback_port; oauth_authorize / oauth_exchange /
oauth_refresh_token rebuild the localhost redirect_uri with this port via
the new OAuthConfig::with_callback_port helper so the auth URL stays in
sync with the live listener. Anthropic's remote redirect_uri is left alone.

If every port in the range is busy, the error log now reads:
"Failed to bind OAuth callback server on 127.0.0.1 in port range
1455..=1464".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Destynova2 Destynova2 enabled auto-merge April 28, 2026 06:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant