Skip to content

fix: make upstream egress explicit and resilient - #15

Merged
Anionex merged 2 commits into
Anionex:mainfrom
KuramiWan:codex/upstream-egress-failover
Aug 13, 2026
Merged

fix: make upstream egress explicit and resilient#15
Anionex merged 2 commits into
Anionex:mainfrom
KuramiWan:codex/upstream-egress-failover

Conversation

@KuramiWan

@KuramiWan KuramiWan commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

fix: make upstream egress explicit and resilient

Problem

vision_proxy.py used urllib's default opener for model-upstream requests. On Windows, that implicitly follows the system proxy. If a local Clash proxy such as 127.0.0.1:7890 is configured but stopped, every request through the local vision proxy fails with 502 even though the proxy process itself is healthy.

Fixes #14.

Changes

  • Replaced the implicit urllib opener with explicit stdlib networking (socket + http.client): direct TCP/TLS by default, or an explicitly configured HTTP CONNECT proxy.
  • Added --upstream-proxy / VISION_UPSTREAM_PROXY and --proxy-first / VISION_PROXY_FIRST=1.
  • Added failure-only failover and in-memory sticky routing: only connection-establishment failures switch routes; HTTP responses pass through without replaying the request.
  • Preserved upstream Authorization, ordinary request bodies and model names, and default incremental SSE forwarding.
  • Hardened proxy URL handling:
    • standard port 80 when omitted;
    • no proxy authentication support;
    • rejected credentials are never echoed into errors or logs;
    • IPv6 CONNECT authorities are bracketed correctly;
    • successful CONNECT accepts the 2xx range.
  • Added a TLS-backed CONNECT smoke test covering ambient system-proxy isolation, all-route failure details, CONNECT+TLS failover, sticky routing, Authorization/body preservation, HTTP 503 pass-through, and incremental SSE.
  • Expanded CI to Ubuntu/Python 3.11 + 3.14 and Windows/Python 3.14.
  • Updated the English/Chinese README, deployment guide, and ADR with the explicit-egress contract.

Verification

python3 -m py_compile vision_proxy.py vision_client.py ground.py detect.py bin/glance bin/trace bin/crop tests/smoke_test_egress_failover.py
python3 tests/test_image_rewrite_shapes.py
python3 tests/test_focus_hint.py
python3 tests/test_anthropic_rewrite.py
python3 tests/smoke_test_proxy.py
python3 tests/smoke_test_egress_failover.py
python3 tests/test_vision_client.py
python3 tests/test_restore_ui_playbook.py
git diff --check

All passed locally on Python 3.12. The egress smoke test and compile checks also passed locally on Python 3.11.

This is a non-UI proxy/networking change, so there is no meaningful browser E2E path. The substitute verification uses local TLS upstream and CONNECT proxy fixtures to exercise the affected request path end to end.

@Anionex Anionex changed the title feat: explicit upstream egress with failure-only failover fix: make upstream egress explicit and resilient Aug 13, 2026
@Anionex
Anionex marked this pull request as ready for review August 13, 2026 18:29
@Anionex
Anionex merged commit ecaedd3 into Anionex:main Aug 13, 2026
4 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b3665c670

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread vision_proxy.py

def _tcp_connect(self, address, deadline):
try:
return socket.create_connection(address, timeout=self._remaining(deadline))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Bound DNS lookup by the egress deadline

When resolving either the upstream or explicit proxy through a slow or unavailable DNS resolver, socket.create_connection() calls getaddrinfo() before applying this timeout to a socket. The lookup can therefore block well beyond the promised 5-second deadline, preventing the next egress candidate from being attempted and defeating failover. Resolve under the deadline separately (for example in a bounded worker) before connecting.

Useful? React with 👍 / 👎.

@Anionex

Anionex commented Aug 13, 2026

Copy link
Copy Markdown
Owner

thx🚀

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.

[Bug] vision proxy implicitly follows the Windows system proxy; a dead Clash (127.0.0.1:7890) breaks the whole chain

2 participants