Skip to content

fix(proxy): avoid per-app port collision on the default proxy port#305

Open
mvanhorn wants to merge 1 commit into
SaladDay:mainfrom
mvanhorn:fix/290-proxy-per-app-port-conflict
Open

fix(proxy): avoid per-app port collision on the default proxy port#305
mvanhorn wants to merge 1 commit into
SaladDay:mainfrom
mvanhorn:fix/290-proxy-per-app-port-conflict

Conversation

@mvanhorn

Copy link
Copy Markdown
Contributor

Summary

Starting a second proxy worker failed with bind proxy listener failed: Address in use (os error 98), surfaced as codex worker exited before hello. The root cause is a port collision: default_app_preferred_port returned 15721 for every app type except codex/gemini, which is the same port claude uses. So once the Claude proxy was bound to 15721, any other app that fell through to the default tried to bind the same port and the worker died before it could start.

This gives non-Claude apps a distinct default port (15724) and stops the legacy stored-port lookup from handing back Claude's default port to a different app. As defense in depth, the proxy listener now retries on an ephemeral port if its configured port is already in use, instead of failing the worker outright.

Why this matters

The collision made it impossible to run a second app's proxy alongside Claude's: the worker exited immediately with an OS-level "Address in use" error and no recovery path. Issue #290 reports exactly this failure. Separating the default ports removes the collision at the source, and the ephemeral fallback keeps a worker alive even if its preferred port is taken by something else on the machine, so the previously-bound port is preserved when free and a free port is chosen otherwise.

Changes

default_app_preferred_port now returns 15724 for the fallback app type so it no longer overlaps Claude's 15721, and the legacy listen-port lookup no longer returns Claude's default port for a non-Claude app. In server.rs, a bind that fails with AddrInUse on a fixed port retries once against port 0 (ephemeral), logs a warning, and reports the real bound address; other bind errors and the explicit-ephemeral case are unchanged.

Testing

Added coverage in proxy_database.rs and proxy_service.rs for the distinct-default-port behavior and the in-use fallback. Run locally: cargo test --test proxy_database (17 pass) and cargo test --test proxy_service (30 pass). cargo fmt --check is clean.

Fixes #290

@CodeCatMeow

Copy link
Copy Markdown

感谢这个修复。我不是这个项目的贡献者,这里补充一个方向性的看法,供维护者参考。首先说明:下面这段理解是我结合 issue / PR 描述,并用 AI 辅助阅读了部分代码后得到的结论,无法保证每个实现细节都完全准确;如果我理解有偏差,非常抱歉给维护者带来了干扰。

我目前的理解是, #290 表面上是端口冲突,但更深层的问题可能不是“默认端口选得不对”,而是多应用代理的运行时模型本身:

  • 当前修复主要是在处理“某个 app 的 worker 端口被占用”这个症状;
  • 但从代理本身的职责看,cc-switch 更自然的模型,可能应该是:
    一个统一的本地代理入口承接所有应用流量,在服务内部根据协议 / 路由识别请求类型,再完成 Claude、Codex、Gemini 的分流、鉴权注入与上游转发;
  • 换句话说,多应用支持更适合建立在 “单代理入口 + 按请求路径 / 协议分流”之上,而不是建立在“每个 app 一个监听端口 / 一个 worker”之上。

我之所以这样想,还有一个原因:我也让 AI 辅助对比看了一下这个项目 https://github.com/farion1231/cc-switch ,AI 给我的结论是:cc-switch更接近“一个统一本地代理入口 + 内部分流”的思路,也就是通过协议 / 路由识别来区分 Claude、Codex、Gemini 的流量,而不是按 app 拆成独立监听端口。从使用上来看,cc-switch也是仅需要一个本地端口即可代理laude、Codex、Gemini等app。当然,这一点我同样没有逐行人工完整核对,所以这里只把它当作一个AI 辅助得到的对比线索。

所以我个人的建议是:

  1. 这个 PR 作为修复是有价值的,至少能避免当前直接报 Address in use;
  2. 但从长期看,也许值得考虑把代理模型设计为:
  • 一个统一的本地代理入口;
  • Claude / Codex / Gemini 共享同一个监听地址和端口;
  • 在服务内部按请求路径、协议形状、适配器来识别和分流;
  • app 维度保留 takeover / provider / failover 等状态,而不是继续把 app 维度绑定到独立监听端口或独立 worker。

如果维护者认为这个方向是合理的,也许可以把它作为一个后续 issue / follow-up refactor 单独跟踪。当前这个 PR 解决的是“怎么别撞端口”,而更长期的问题可能是“是否应该继续把多应用支持建立在多 worker / 多端口之上”。

以上只是一点微不足道的建议,仅供参考。

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.

Error: codex worker exited before hello: Error: bind proxy listener failed: Address in use (os error 98)

2 participants