Skip to content

[Bug] IPv6 holder health URL falsely reports a healthy listener as wedged #6174

Description

@Afloat16

Description *

Affected source

Package version 0.1.213, current master 33104b1f9545caeb0344193009917ceef9969682. This is a source-level regression report for this exact revision.

Problem

holderProbeOrigin concatenates a bind hostname and port without bracketing IPv6 literals. For ::1 it produces http://::1:PORT, which fetch cannot parse. The failed health probe then labels a healthy listener as wedged/dead and advises killing it during duplicate starts or terminal EADDRINUSE handling.

Proposed correction

Bracket raw IPv6 literals when constructing the probe authority, while preserving IPv4, DNS names, already bracketed literals, and existing wildcard-to-loopback behavior.

Regression coverage

The prepared branch includes focused regression coverage in tests/port-owner.test.ts.

Category K (startup / port diagnostics). Suggested triage: p2, scope/other.

bili version *

0.1.213 — source revision 33104b1

Usage mode *

Other / not sure

Client and version *

Direct Node.js v24.22.1 regression harness; no external agent client session.

Operating system *

Linux x86_64; deterministic local regression fixtures.

Upstream and model *

Not applicable: local port-holder diagnostics, before any model request.

Compression-related config (optional)

Log excerpt (strongly recommended)


/acp or acp_status output (optional)


Activity

  1. Afloat16 commented on Nov 11, 2026

    @Afloat16
    ContributorAuthor

    Proposed fix: #6182. The PR includes focused IPv6 regression coverage and preserves the existing IPv4, hostname and wildcard-to-loopback behavior.

  2. ranxianglei commented on Nov 11, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    Duplicate screening: this thread already has its fix PR — #6182 ("fix: bracket IPv6 addresses in port-holder health probes"), opened by the reporter and referencing Fixes #6174. Per the one-fix-PR-per-thread rule I did not open a parallel PR; I independently reviewed #6182 against the actual diff (not just its description) and verified CI live.

    What I verified

    • Bug is real and reachable at current master (e3220adc): src/port-owner.ts:227 builds `http://${h}:${port}` with no IPv6 bracketing, so --host ::1 (a valid option, src/cli.ts:196) yields the unparseable http://::1:PORT; the EADDRINUSE / duplicate-start health probe then throws → is caught → reads as "no response" and mislabels a healthy listener as wedged/dead.
    • The fix is correct and minimal (+2/−1): const authority = h.includes(":") && !h.startsWith("[") ? [${h}] : h; — brackets raw IPv6 literals while preserving IPv4, DNS names, already-bracketed literals, and the existing wildcard→loopback mapping. I hand-traced all four cases plus zone-ID ([internal-ip6]%eth0) and empty-host edges; none regress.
    • It matches an existing pattern: src/server.ts:1276 (boundOrigin) already does exactly this bracketing ("bracket bare IPv6 literals so the file always holds a valid URL") — so this closes an oversight in holderProbeOrigin, not a new approach.
    • Tests have teeth: an exact-string unit assertion set and a live ::1 server integration test asserting both directions (serving vs. not-wedged), consistent with the Test Blind Spot Retrospective: Reinforcing Negative Assertion Discipline, Tightening Fake Upstream Strictness, Thin Inst | 测试盲区复盘:补反向断言纪律、假上游严格化、薄安装冒烟、形态矩阵与生命周期覆盖 #3617 gate discipline.
    • CI green on the pushed head (9af4b7f): 30 checks, 0 failed / 0 pending across ubuntu×node 22/24 and windows×node 22; the skipped e2e lanes are gated suites not triggered by this change (no request-pipeline files touched). Branch protection leaves it blocked pending a human review/merge.
    • Added triage labels to [PR] fix: bracket IPv6 addresses in port-holder health probes #6182: bug / p2 / scope/other (matching the suggested triage).

    Related instances found during review (surfaced, not folded in): the same raw-host-no-bracket interpolation appears at a few other host→origin builders — src/launcher.ts:389 (proxyOrigin), src/server/admin.ts:323/552/805 (browser-facing web-UI origins), src/startup-failure-page.ts:54, and the startup banner via displayHost (src/server.ts:1290). These are browser/display-facing (symptom: malformed UI/banner URLs under an IPv6 bind) — a different failure domain from the reported server-side false-diagnosis — so I kept #6182 scoped to the reported repro. Happy to fold them into this PR or track separately; your call.


    结构化八问(PR #6182 审核结论)

    1. 是什么问题:IPv6 回环绑定(--host ::1)时,端口占用诊断把健康的监听者误判为「卡死/死亡」,并建议 kill 掉它。
    2. 什么原因导致的:holderProbeOrigin(src/port-owner.ts:227)拼 http://host:port 时没给裸 IPv6 字面量加方括号,::1 生成 fetch 无法解析的 http://::1:port;探测抛错被吞成「无响应」,于是误判。根因在 URL 生产点,不在下游探测层。
    3. 影响范围与严重程度:仅当同时满足「用 IPv6 主机绑定(非默认 127.0.0.1)」且「发生重复启动/EADDRINUSE」才触发;默认用户不受影响。p2——特定条件下给出错误处置建议(可能诱导误杀健康实例),非全面不可用。
    4. 怎么修复的:在 URL 生产源头加方括号——裸 IPv6(含冒号且未以 [ 开头)包成 [addr],保留 IPv4/DNS/已带括号/wildcard→loopback 既有行为;与 src/server.ts:1276 boundOrigin 既有写法一致。
    5. 是否有回归问题:无。逐条核过既有行为——wildcard→loopback、IPv4 透传、DNS 透传均不变,只有原本就坏的裸 IPv6 分支现在变好;两个调用点(server.ts:734 早期孪生冲突退出、server.ts:1681 post-bind 终态分支)都受益;无 fail-fast 变吞错、无阈值/时序/输出格式漂移。
    6. 是否破坏缓存:不涉及。纯启动期端口诊断路径,不触碰压缩缓存/重试/游标/去重键。
    7. 是否改了设计方向:不涉及。单函数最小修正,未改架构/边界/数据流。
    8. 是否引入了新的配置:不涉及。无新 env/config/默认值。

    判定:#6182 在其报告范围内正确、完整地修复了根因,测试双向有牙、CI 全绿,可合入(合并由人工执行)。另发现的浏览器/展示侧同类站点建议单独跟进或并入,待你定夺。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions