Repository navigation
[Feature] SOCKS5 proxy support for outbound provider calls - and fail fast on unsupported proxy schemes #2894
Copy link
Copy link
Closed
Labels
account-poolOAuth, credentials, Codex pool, quota, failover, plansOAuth, credentials, Codex pool, quota, failover, plansenhancementNew feature or requestNew feature or request
Description
Area
Proxy and routing
What are you trying to accomplish?
Two related needs when reaching model upstreams from restricted networks:
- Route all provider traffic through an outbound HTTP or SOCKS5 proxy, including SOCKS5 endpoints exposed by local tunnel clients (sing-box, v2ray/Xray,
ssh -D, Tor), which is the most common listen mode for those tools. - Route individual providers through different proxies — and others directly. Example (real): Google Antigravity must exit through a US proxy, xAI through a residential/EU proxy, and zai/GLM should go direct from the local network, because a foreign exit adds latency and triggers regional risk controls on a Chinese upstream. One global
proxyfor everything cannot express this — exactly the split Omniroute has: a common default proxy plus a per-provider override with a "direct / default / custom" choice.
What prevents this today?
config.proxysupports HTTP(S) proxy URLs only. Bun's fetch (the bundled runtime) does not implement SOCKS —socks5://is not negotiated (upstream Add SOCKS support oven-sh/bun#16812 open), and worse, Bun silently treats the unknown scheme as HTTP instead of failing (Bun treating unknown proxy protocol as HTTP instead of fail oven-sh/bun#11343). The config layer accepts any string without scheme validation, soproxy: "socks5://127.0.0.1:1080"is stored, mirrored intoHTTP_PROXY/HTTPS_PROXYbyapplyProxyEnv(), and only produces confusing network-level errors at request time. A user with only a SOCKS5 endpoint must spin up an extra HTTP listener (privoxy/glider/sing-box mixed port) as a workaround.proxyexists only at the top level ofOcxConfig(src/types/config.ts:506).OcxProviderConfig(src/types/provider.ts:138) has no proxy field of any kind, so per-provider egress is impossible: either everything goes through the single proxy (regional upstreams suffer), or nothing does (restricted upstreams are unreachable).
What should OpenCodex do?
A. Per-provider proxy with a global default (Omniroute-style two-level model):
- Global
proxy/noProxykeep working exactly as today and become the default for every provider. - Each provider gains
proxy+noProxywith three effective states:- unset → inherit the global default;
- explicit value → use this provider's own proxy (fully overriding the global for that provider);
null/""→ force direct egress, exempting the provider from the global proxy.
- Effective proxy resolution logged observably:
ocx inspect(orocx provider test <name>) should show which proxy each provider resolves to (global / custom / direct), andocx doctorshould probe each configured proxy. - Apply symmetrically to OAuth-backed providers (Antigravity, xAI, ...) — token refresh and quota probes must use the same per-provider egress as model calls, or the account looks healthy while model calls fail.
B. SOCKS5 support:
- Accept
socks5://(local DNS) and ideallysocks5h://(remote DNS — normally what users behind restrictive networks want) in both the global and per-provider fields; RFC 1928 handshake + RFC 1929 user/pass auth. - At minimum, fail fast: validate the scheme at config-set/apply time and reject
socks5*://with a clear message ("SOCKS proxies are not supported yet — use the HTTP inbound of your tunnel client") instead of the current silent misrouting.
Example usage or interface
# Global default stays as-is (all providers inherit it):
ocx config set proxy "http://user:pass@proxy.example.com:8080"
# Per-provider overrides (new):
ocx provider edit google-antigravity --proxy "socks5://127.0.0.1:1080"
ocx provider edit xai --proxy "http://residential.example.com:3128"
ocx provider edit zai --proxy "" # force DIRECT, exempt from the global proxy// ~/.opencodex/config.json
{
"proxy": "http://user:pass@proxy.example.com:8080",
"providers": {
"google-antigravity": {
"baseUrl": "https://daily-cloudcode-pa.googleapis.com",
"adapter": "google-antigravity",
"proxy": "socks5://127.0.0.1:1080" // inherits user/pass/noProxy only from itself
},
"zai": {
"baseUrl": "https://api.z.ai/api/coding/paas/v4",
"adapter": "openai-chat",
"proxy": null // explicit direct: ignores the global proxy
}
}
}Effective-resolution view (illustrative):
$ ocx provider test google-antigravity
egress: socks5://127.0.0.1:1080 (provider override) — handshake ok, exit IP 45.xx.xx.xx
$ ocx provider test zai
egress: direct (provider override: null) — exit IP 91.xx.xx.xxAlternatives or workarounds
- Run an HTTP listener next to the SOCKS5 endpoint (sing-box mixed inbound / privoxy / glider) and point the global
proxyat it — works, but adds a moving part per machine and still cannot split providers onto different exits. - Multiple OpenCodex instances with different global
proxyvalues and split catalogs — heavy, doubles the service/dashboard footprint, and clients must know which port to call. - Transparent proxy (TUN mode / firewall rules) routing by process or destination — OS-level, brittle, and cannot distinguish providers that share one upstream domain.
Additional context
- Upstream Bun: oven-sh/bun#16812 — Add SOCKS support (open), oven-sh/bun#11343 — unknown proxy protocol silently treated as HTTP.
- Related OpenCodex issue: Feature: auto-detect Windows system proxy with smart fallback ("proxy": "auto") #1525 ("proxy": "auto" — Windows system-proxy auto-detection); the same scheme-validation work applies there.
- Implementation note: SOCKS5 likely needs a custom connector around
Bun.connect/netfor proxied calls until Bun grows SOCKS support (community drop-in implementations exist and are MIT-licensed). - Omniroute reference for the two-level proxy model: global default + per-provider override with direct/default/custom.
Checks
- I searched existing issues and documentation.
- This request describes a concrete OpenCodex workflow rather than merely naming a desired technology.
- I removed secrets and personal data.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
account-poolOAuth, credentials, Codex pool, quota, failover, plansOAuth, credentials, Codex pool, quota, failover, plansenhancementNew feature or requestNew feature or request
Activity
github-actions commentedon Aug 29, 2026
Issue reopened
The report now contains the information required by the automated check. Thanks for updating it.
lidge-jun commentedon Aug 29, 2026
리뷰 · 우선순위 55 / 80
설명
제한된 네트워크에서 업스트림으로 나갈 때 (1) HTTP만이 아니라 SOCKS5(
sing-box,ssh -D, Tor 등)로 전체 provider 트래픽을 보내고, (2) 프로바이더마다 다른 프록시/다이렉트를 고르고 싶다는 기능 요청입니다. 오늘config.proxy는 HTTP(S) URL만 가정하고, Bun fetch는 SOCKS를 구현하지 않으며(미구현 이슈 oven-sh/bun#16812), 모르는 스킴을 HTTP로 조용히 취급해 실패가 늦어집니다.현재
dev에서src/config.ts의 proxy→HTTP(S)_PROXY 미러링과src/cli/doctor.ts의config.proxy진단이 이 표면입니다. SOCKS5 문자열을 검색해도 제품 경로에 정식 지원은 없습니다. Omniroute식 "기본 프록시 + 프로바이더별 override(direct/default/custom)"는 account-pool 라벨과도 맞닿지만, 지금은 라우팅 전략(#2050 combo, #2875 kiro quota)과 다른 전송 계층 요구입니다.구현 난이도가 큽니다. Bun 런타임이 SOCKS를 안 하면
undici/socks-proxy-agent같은 우회 fetch 스택을 프로바이더별로 갈라야 하고, 네이티브 fetch에 의존하는 경로가 많으면 구멍 mid-flight가 생깁니다. "미지원 스킴은 즉시 실패"는 상대적으로 싸고 지금 당장 가치가 있습니다. doctor가socks5://를 보고 HTTP로 오인하지 않게 하는 가드가 첫 단계로 적합합니다.types/config 분할 캠페인과 겹칩니다.
config.proxy스키마·프로바이더별 override를 넣으면types.ts/config.ts대형 PR과 충돌하기 쉽습니다. 분할이 끝나기 전에 큰 스키마 확장 PR이 오면 close-don't-rebase를 권하는 기존 방침이 적용될 수 있습니다.경로 config.proxy / src/config.ts - HTTP(S)만 env로 미러. socks5는 Bun이 협상하지 않아 연결이 이상하게 실패하거나 오인됩니다.
경로 src/cli/doctor.ts config.proxy - 스킴 검증을 강화해 SOCKS/unknown을 명시적으로 거부·경고하는 편이 안전합니다. 전체 SOCKS 구현보다 우선 가능합니다.
경로 프로바이더별 프록시 - 스키마·GUI·풀 라우팅까지 범위가 커집니다. Omniroute 패리티를 한 PR에 넣지 말고 단계를 나누어야 합니다.
경로 런타임 - Bun SOCKS 미지원이 막히면 Node undici 전환 또는 외부 forward proxy(HTTP로 SOCKS에 다시 붙는 local relay) 문서화가 현실적 대안입니다.
메인테이너의 판단이 필요한 지점
너의 추천
enhancement로 유지하되, 먼저 "SOCKS/unknown 스킴 fail-fast + doctor 경고"만 작은 PR로 받고, 실제 SOCKS 전송과 per-provider override는 config 분할 이후 설계 이슈로 분리하세요. 지금 큰 구현 PR이 오면 분할 캠페인에 무효화될 가능성이 있어 close-don't-rebase 대상이 될 수 있다고 명시합니다.
이 댓글은 grok-bot이 작성했습니다
franz101 commentedon Aug 29, 2026
oven-sh/bun#40461
Ingwannu commentedon Aug 29, 2026
Thanks, this materially improves the upstream path. I checked oven-sh/bun#40461: it is currently open, unmerged, and blocked on review at head 9a66e8508862b55f97851a2726824d083d2d7d12, so it is not available in the Bun 1.3.14 runtime OpenCodex currently pins.
The near-term order therefore stays the same: first reject unsupported or unknown proxy schemes at config/apply time and surface a precise doctor message instead of letting Bun silently treat SOCKS as HTTP. Once the upstream PR is merged into a Bun release we can actually pin and test, we should re-evaluate native socks5 and socks5h support before introducing a second fetch stack. Per-provider direct/default/custom egress remains a separate transport-design slice because it must cover model calls, OAuth refresh, quota probes, discovery, and sidecars consistently.
lidge-jun commentedon Sep 20, 2026
Post-main scope consolidation at 7c625fc (2.60.0): the global SOCKS5 HTTP/SSE transport is delivered through #4986 and its content-coding / lifecycle follow-ups (#5070, #5126, #5127). This is not a remaining request to implement global SOCKS5 from scratch.
Keep this issue open for its distinct per-provider egress requirement: an explicit inherit/direct/proxy decision, correct noProxy and protocol handling, and the same decision for inference, provider discovery, quota and OAuth refresh without leaking proxy credentials. #3901 is the existing HTTP(S)-override implementation candidate, not proof that every SOCKS/control-plane case is delivered. #5087 covers the separate prerequisite that a proxy must actually apply to the destination before direct-route DNS pinning can be relaxed.
Use those existing PRs rather than opening another competing egress implementation. Preserve unsupported transport cases as explicit refusals; a missing effective proxy must not silently change the admitted direct destination.
fix(proxy): resolve the provider route at the physical send
feat(proxy): decide a provider's outbound egress per request
fix(proxy): resolve the provider route at the physical send
Per-provider outbound egress: direct, inherit, HTTP(S) and SOCKS5 per…
lidge-jun commentedon Sep 24, 2026
Closing as delivered. The global SOCKS5 transport shipped through #4986 and its follow-ups (#5070, #5126, #5127), and the remaining per-provider egress requirement recorded in the 2026-09-20 scope note landed later that day in #5289:
providers.<name>.proxyaccepts absent (inherit),"direct"/null,http(s)://andsocks5(h)://, andproviders.<name>.noProxyapplies to whichever route resolved. The authority issrc/lib/provider-egress.tsondev. If a specific provider or transport still ignores its per-provider route, please open a new issue with that route and a redacted config.