Skip to content

[PR] fix: ICMP no-route errno family missing from transport failure taxonomy (#2465) - #2468

Closed
ranxianglei wants to merge 5 commits into
masterfrom
2026-10-09_upstream-unreachable-kind
Closed

ranxianglei wants to merge 5 commits into
masterfrom
2026-10-09_upstream-unreachable-kind

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Closes #2465

What

The ICMP no-route errno family (EHOSTUNREACH / ENETUNREACH / EHOSTDOWN) was missing from the transport failure taxonomy: classifyUpstreamFailure returned unknown for them, and none of the three consumers of the table accept unknown — no bounded transparent replay (isFailFastUpstreamKind), no web banner (ALERT_KINDS), and a hint that asked users to report what bili had already logged in full. Field evidence from the issue: 305 failures in a 35-minute window, 301 with zero replay, zero banner entries.

Two commits:

  1. New kind upstream-unreachable (8dce934) — covers EHOSTUNREACH/ENETUNREACH/EHOSTDOWN; wired through all six surfaces: classify table, fail-fast set, alertable set, UPSTREAM_FAIL_HINTS, TRANSPORT_FAILURE_LABELS (in-band client label), and zh/en i18n alert.hint.upstream_unreachable with its client.ts switch case (Web UI 英文翻译开关(i18n)— 社区 PR #807 (@amirzamli) #1024 lint coupling). Kept separate from connect-timeout deliberately: immediate ICMP rejection vs deadline expiry carry different remediation (routing/ACL check vs upstream health/timeout budget) — folding would regress the [Enhancement] 代理上游连接异常缺乏区分性诊断(代理 reset vs 超时 vs 客户端 abort)+ 握手层健壮性评估 #1263 distinguishable-remedies goal.
    • P1a-2: the unknown hint/i18n key now state facts ("full masked error chain is on this log line") instead of demanding a user report.
    • P2: new test gate — every alertable kind must have a dedicated i18n hint key in both locales.
    • New coverage gate: every connect-stage errno (refused / DNS×2 / timeout / no-route×3) must classify to a fail-fast AND alertable kind — a future missing code fails CI instead of the field.
  2. One-line logging for classified transport deaths (c4f8e5b) — the dispatch catch printed message + full stack for every classified transport failure (field: 305 × 7 stack frames of pure noise); the envelope now tags biliTransportFailure so that class logs one line (the formatUpstreamError output already carries kind/code/errno/syscall/address/hint). The Accepted request is never dispatched and never answered — client hangs forever (no request-scoped deadline) #806 stack rule is untouched for all other error classes.

Behavior changes (old → new)

Surface Old New Why
Replay policy for EHOSTUNREACH/ENETUNREACH/EHOSTDOWN first-attempt throw, retry budget never consumed bounded transparent replay within existing BILI_REPLAY_RETRY_MAX same criterion as #1454/#1693: TCP handshake provably never completed, nothing reached upstream
Log kind= for these errnos unknown upstream-unreachable classification fix
Web banner (/__bili/overview) no entry for this family entry appears (kind upstream-unreachable) it IS a "cannot connect" class
UPSTREAM_FAIL_HINTS.unknown / i18n alert.hint.unknown "report with full error chain" factual: chain already on the log line P1a-2: stop demanding reports for what bili logs
Dispatch-catch log for classified transport failures message + full stack single line P1b: 305×7 frames of noise; #806 forensics intact elsewhere

Not changed: ETIMEDOUT stays upstream-timeout (deliberate conservative design, out of scope); P1a-1 (splitting pre-response unknown) is NOT implemented — undici wraps connect-phase and body-phase socket deaths identically, so the stage is not determinable from error shape alone, and after this PR the observed field family is covered. No new config surface (reuses the existing BILI_REPLAY_RETRY_MAX budget); the new i18n key is required by the #1024 lint coupling, not a config knob.

Tests

  • tests/upstream-fail.test.ts: taxonomy + fail-fast assertions extended; new connect-stage errno coverage gate.
  • tests/upstream-alerts.test.ts: EHOSTUNREACH record enters the alert path; new i18n-key consistency gate.
  • Targeted local runs green: upstream-fail, upstream-alerts, web-i18n, upstream-error-log, main-path-transport-retry, log-mask (56 tests) + typecheck + build clean (dist-import-annotations OK).
  • codex real-client e2e: not runnable in this sandbox (no model endpoint — E2E_CHECK=1 preflight shows upstream /models probe: unreachable); CI runs it automatically since the diff touches src/server.ts / src/loop/**.

awork added 2 commits October 9, 2026 13:53
EHOSTUNREACH/ENETUNREACH/EHOSTDOWN fell through classifyUpstreamFailure to
'unknown', which none of the three taxonomy consumers accept: no bounded
transparent replay (isFailFastUpstreamKind), no web banner (ALERT_KINDS),
and a hint that asked users to report what bili already logged in full.

New kind 'upstream-unreachable' (separate from connect-timeout: immediate
ICMP rejection vs deadline expiry, different remediation): added to the
classify table, fail-fast set, alertable set, UPSTREAM_FAIL_HINTS,
TRANSPORT_FAILURE_LABELS, and zh/en i18n alert.hint.upstream_unreachable
with its client.ts switch case (#1024 lint coupling).

P1a-2: the unknown hint/i18n key now state facts (full masked error chain
is on this log line) instead of demanding a user report.

Tests: taxonomy + fail-fast assertions extended; new coverage gate asserts
every connect-stage errno lands in a fail-fast AND alertable kind; new
P2 gate asserts every alertable kind has a dedicated i18n hint key in both
locales.
The dispatch catch printed message + full stack for every classified
transport death; field evidence showed 305 x 7 stack frames of pure noise
(the one-line formatUpstreamError output already carries kind/code/errno/
syscall/address/hint). The envelope now tags biliTransportFailure so the
catch logs that class as one line; the #806 stack rule stays intact for
all other error classes.
@ranxianglei
ranxianglei force-pushed the 2026-10-09_upstream-unreachable-kind branch from c4f8e5b to 635e9a7 Compare October 9, 2026 05:56
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-09_upstream-unreachable-kind (119ebc6)

Option A — Install this exact build (recommended)

npm install -g billion-context@0.0.2681-pr.2468.e6f7117

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr2468.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 冲突已解决并验证(本次为「上游 PR 冲突」唤醒的处理结论)

情况:PR #2468 分支被系统标为 dirty(与 master 冲突)。拉取最新 master 后确认根因——分支 fork 点之后 master 合入了 release v0.1.189 及一批变更,其中 #1440 P2 cut 4(1f3b14558)把 forward() 的 relay 区从 src/server.ts 抽成了 src/server/relay.ts;而本 PR 第 2 个提交(c4f8e5b)改的正是那段里的一行 throw,于是产生语义冲突(同文件两侧改 + 代码搬家)。

处理:rebase 两个提交到当前 master。唯一需手工解决的冲突点:c4f8e5b 第二处改动(给 upstream request failed 的 throw 打 biliTransportFailure 标记)原在 server.ts,master 已把它搬到 src/server/relay.ts:479 —— 我把这行重定位到 relay.ts,并从 server.ts 删除 master 已搬走的整段(~1476 行,现已是独立模块);dispatch catch 的单行日志改动照常落在 server.ts。第 1 个提交(分类)自动干净应用。

验证(针对与远端 head 635e9a7 逐字节一致的树):

  • 我的独立 rebase 结果与远端已推送的 head 635e9a7 逐字节相同(两次独立解冲突收敛,交叉印证正确性);
  • tsc --noEmit 通过(kernel / pi-subagents 随 pre-hook 构建成功);
  • 定向测试 59/59 全绿(upstream-fail / upstream-alerts / web-i18n / upstream-error-log / main-path-transport-retry / log-mask);
  • npm run build 成功,dist-import-annotations: OK;
  • 远端 CI(head 635e9a7):28 项中 24 项 success(kernel-guard / pi-subagents-guard / unused-exports / build-artifact / 各 hermetic + real-lane e2e 全绿),剩 4 个 test 矩阵(ubuntu/windows × 22/24)仍在跑,零失败。

结构化八问:

  1. 是什么问题:不是新缺陷,是上游推进造成的合并冲突(PR 分支 vs master)。
  2. 什么原因:master 侧 audit: 全仓冗余审计与重构计划——server.ts 解体、客户端矩阵去重、死代码、AGENTS.md 模块图腐化 #1440 P2 cut 4 把 forward() 挪进 src/server/relay.ts,与本 PR 在该函数内的一处单行改动撞车。
  3. 影响范围 / 严重度:仅影响该 PR 能否干净合并;不触及任何已发布行为。严重度低(纯合并 + 位置搬移,无逻辑改动)。
  4. 怎么修的:rebase,并把那处跨文件搬移的改动重定位到 relay.ts,其余按 master 现状落位。
  5. 是否有回归:无。rebase 零逻辑改动;server.ts 删掉的均为 master 主动抽出的内容;本 PR 六处分类 / 日志改动的意图完整保留。
  6. 是否破坏缓存:不涉及(未动持久化格式 / session id / 缓存键)。
  7. 是否改了设计方向:不涉及。
  8. 是否引入新配置:不涉及(rebase 未新增配置面;原 PR 亦未新增,复用 BILI_REPLAY_RETRY_MAX)。

为何没有再 force-push:远端分支已处于正确且已验证的状态(与我独立 rebase 逐字节一致),再推只会造成 SHA 抖动,且有极小概率与并发解冲突方互相覆盖,无实际收益。若你希望我用本会话的 rebase 覆盖一次,我可照做。

判定:冲突已解除(mergeable=True,state 由 dirty→blocked,blocked 仅为待 CI / 人工 review,非文本冲突),代码与 CI 均健康;等 4 个 test 矩阵转绿即可由你合并 #2468 。

awork added 3 commits October 9, 2026 14:09
/#2465 taxonomy

Ported from the #2467 review: connect-timeout (own kind since #1453) had no
row, upstream-timeout still claimed 'or connect timed out', dns said 'not
retried' though it has been fail-fast/replayed since #1453.
… fetchWithRetry loop (#2465)

Ported from #2467: the coverage gate pins policy membership but did not
exercise the retry loop end-to-end for the new kind.
@ranxianglei

Copy link
Copy Markdown
Owner Author

(ai: zhipuai-lb/glm-5.3)

陈旧票清理(第三批动作①):目标 issue #2465 已关。为避免重复劳动关闭本 PR;如判断有误请直接 reopen 并留言。

@ranxianglei ranxianglei changed the title fix: ICMP no-route errno family missing from transport failure taxonomy (#2465) [PR] fix: ICMP no-route errno family missing from transport failure taxonomy (#2465) Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant