Skip to content

fix(proxy): direct-connection fallback for control-plane ops when a pinned proxy is unreachable - #3906

Merged
diegosouzapw merged 3 commits into
cheaper-inference:release/v3.8.26from
zhiru:fix/proxy-control-plane-direct-fallback
Jun 15, 2026
Merged

diegosouzapw merged 3 commits into
cheaper-inference:release/v3.8.26from
zhiru:fix/proxy-control-plane-direct-fallback

Conversation

@zhiru

@zhiru zhiru commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Problem

Linking a Claude (OAuth) account can fail with a generic "Internal server error" when the account is pinned to a proxy (proxy_assignments) that has gone dead. The OAuth code/token exchange and the provider connection test both run inside runWithProxyContext, whose T14 fast-fail throws PROXY_UNREACHABLE (503) when the pinned proxy can't be reached — with no fallback. Free-proxy pools go stale quickly, so a single dead egress silently breaks linking a new account, even though the upstream is reachable directly.

Observed in production logs:

[Proxy Fast-Fail] Proxy unreachable: http://<dead-proxy>:7890
Error testing connection: Error: [Proxy Fast-Fail] Proxy unreachable: http://<dead-proxy>:7890

Fix

Make control-plane operations degrade to a direct connection instead of throwing when the pinned proxy is unreachable:

  • runWithProxyContext gains an opt-in directFallbackOnUnreachable. When the reachability check (or the ?family pre-check) fails, it logs a warning and runs the callback with the proxy context cleared (proxyContext.run(null, fn) → direct egress) instead of throwing.
  • New runWithProxyContextOrDirect(proxyConfig, fn) wrapper applies it.
  • Wire it into the control-plane flows only:
    • OAuth device-code / token exchange / refresh (/api/oauth/[provider]/[action])
    • provider connection test (/api/providers/validate)

Data-plane chat keeps strict pinning via the unchanged runWithProxyContext, so per-account egress-IP isolation is preserved where it matters. Control-plane reachability is prioritized over IP pinning — a dead pinned proxy must not block linking/validating an account.

Verification

  • tests/unit/proxy-fetch.test.ts: throw-by-default on an unreachable proxy, opt-in fallback runs the callback directly, and runWithProxyContextOrDirect degrades to direct. node --test green.
  • eslint + typecheck:core clean.

🤖 Generated with Claude Code

… proxy is unreachable

A dead proxy pinned to an account (via proxy_assignments) made account linking
fail with a generic "Internal server error". The OAuth code/token exchange and
the connection test run inside runWithProxyContext, whose T14 fast-fail throws
PROXY_UNREACHABLE (503) when the pinned proxy can't be reached — with no
fallback. Free-proxy pools go stale, so a single dead egress silently breaks
linking a new account even though the upstream is reachable directly.

- runWithProxyContext gains an opt-in `directFallbackOnUnreachable`: when the
  reachability/family pre-check fails, it logs a warning and runs the callback
  with the proxy context cleared (direct egress) instead of throwing.
- New runWithProxyContextOrDirect wrapper applies it.
- Wire it into the control-plane flows — OAuth (device code / token exchange /
  refresh) and the provider connection test (validate) — where reaching the
  upstream matters more than per-account egress-IP pinning. Data-plane chat
  keeps strict pinning via runWithProxyContext, so IP isolation is preserved.

Tests: proxy-fetch.test.ts covers throw-by-default, opt-in fallback, and the
wrapper degrading to direct. eslint + typecheck:core clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zhiru
zhiru requested a review from diegosouzapw as a code owner June 15, 2026 15:36

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a fallback mechanism to direct connections when a proxy is unreachable or fails family pre-checks. It adds a new helper function runWithProxyContextOrDirect and updates control-plane operations, such as OAuth actions and provider API key validation, to use this fallback instead of throwing a 500 error. Additionally, unit tests have been added to verify this fallback behavior. I have no feedback to provide as there are no review comments.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@diegosouzapw
diegosouzapw changed the base branch from main to release/v3.8.26 June 15, 2026 17:03
diegosouzapw and others added 2 commits June 15, 2026 14:04
PR cheaper-inference#3906 own growth: oauth/[provider]/[action]/route.ts 916->918 (+2).
Updates the frozen file-size baseline so Fast Quality Gates pass.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks, @zhiru! 🙏 The control-plane / data-plane split is exactly right — OAuth code/token exchange, connection tests, and token refresh shouldn't surface a dead pinned proxy as an opaque 500, while chat keeps strict per-account egress-IP pinning. The runWithProxyContextOrDirect wrapper with the opt-in directFallbackOnUnreachable flag is a clean, backward-compatible shape, and the 3 TDD cases pin the behavior (throws by default / degrades with the opt / wrapper runs direct). All 8 proxy-fetch tests green. I added a small file-size baseline bump (+2). Merging into release/v3.8.26. 🚀

@diegosouzapw
diegosouzapw merged commit 9d846f3 into cheaper-inference:release/v3.8.26 Jun 15, 2026
1 check passed
@diegosouzapw diegosouzapw mentioned this pull request Jun 16, 2026
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
…inned proxy is unreachable (cheaper-inference#3906)

Integrated into release/v3.8.26
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.

2 participants