Skip to content

fix: detect chained bili instances via x-bili-hop marker (#300) - #304

Merged
ranxianglei merged 1 commit into
masterfrom
2026-08-28_bili-hop-marker
Aug 28, 2026
Merged

ranxianglei merged 1 commit into
masterfrom
2026-08-28_bili-hop-marker

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

What

Fixes #300 (root cause of #292): two chained bili instances both ran the compression pipeline on the same request, double-compressing and corrupting session state.

How

Outbound marker (src/server.ts)

  • Each server gets a per-server instanceId = randomUUID().
  • Every request this instance actually processed is stamped x-bili-hop: <instanceId> — the main forward, preflight summarization calls, and compress-loop calls (the latter two share the buildForwardTarget headers).
  • A passthrough forward does NOT claim processing, so a dumb-proxy bili in front of a processing one is unaffected; any inbound marker keeps propagating down the chain verbatim.

Inbound detection (handle())

  • If an inbound request already carries x-bili-hop, an upstream bili already processed it → skip ALL processing (no tool/tag injection, no acp-loop, no session state) and pass it through verbatim.
  • Logs a prominent [warn] naming the upstream instance, distinguishing a foreign chain from a self-loop (upstream pointing back at itself).

Why no false positives in single-instance use: clients never send x-bili-hop — it only appears in bili's own forwards. Claude Code echoing back acp-tagged messages carries no such header, so normal turns are untouched.

Tests

New tests/e2e-bili-chain.test.ts (2 tests):

  1. B in isolation with a foreign marker — body reaches the LLM byte-identical (no tool/tag injection, no re-serialization), exactly one [chain] warn naming the upstream marker, marker propagated unchanged.
  2. Full A→B chain — single-layer processing (A stamps, B warns + passes through), exactly one LLM request, the marker at the LLM is A's (not re-stamped by B).

Pre-flight

  • npm run typecheck — clean
  • npm test — 693 pass (691 existing + 2 new), 0 fail
  • npm run build — success

No dependency changes; package.json version untouched (content branch).

When two bili instances are chained (A forward -> B), both run the
compression pipeline on the same request, double-compressing and
corrupting session state (root cause of #292).

- Outbound: stamp x-bili-hop: <instance-uuid> on every request this
  instance actually processed (main forward + preflight summarization +
  compress-loop calls via shared headers). A passthrough forward does NOT
  claim processing, so a dumb-proxy bili in front of a processing one is
  unaffected; any inbound marker keeps propagating down the chain.
- Inbound: if a request already carries x-bili-hop, an upstream bili already
  processed it. Skip ALL processing (no tool/tag injection, no acp-loop, no
  session state) and pass it through verbatim, logging a prominent [warn]
  naming the upstream instance (self-loop vs foreign chain).
- e2e: two tests — B in isolation with a foreign marker (body reaches the
  LLM byte-identical, no tool/tag injection, marker propagated), and the full
  A->B chain (single-layer processing, B warns, one LLM request, marker is
  A's not B's).

Clients never send this header, so single-instance use is unaffected.
@github-actions

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-08-28_bili-hop-marker (71cfe28)

Option A — Install from npm PR tag (recommended)

npm install -g billion-context@pr-304

Each push to this PR publishes a new version under the pr-304 npm tag.

Option B — Download artifact

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

This comment is automatically updated on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 ework agent · vllm-qwen/qwen3.8-27b

[bot] 已独立审查 PR #304(分支 2026-08-28_bili-hop-marker,commit 71cfe28,基于 master 7742b51,单 commit)。LGTM,可 merge。

Diff 范围:仅 src/server.ts(+62/−6)和新增 tests/e2e-bili-chain.test.ts(+202)。package.json / package-lock.json 零改动,version 未动 —— 符合内容分支规范。

代码审查要点(逐处核对过源码):

  1. 入站检测位置正确(src/server.ts:672-678):链检测在 body 解码之后、session 处理之前;hopMarker !== undefined 时 prepared 保持 null(src/server.ts:776),直接落入 passthrough 转发(src/server.ts:1024)——确认不触 session 状态、不注入工具/标签、不跑 acp-loop。
  2. 打标记逻辑正确:
    • forward()(src/server.ts:1743):仅 prepared !== null(真正处理过)才打 instanceId;passthrough 不声称处理,入站标记原样保留继续向下游传播 —— "哑代理 bili 前置 + 处理型 bili 后置"拓扑不受影响,符合设计。
    • preflightCompressIfNeeded()(src/server.ts:1690):总是打标记(preflight 只在已处理时运行)。
    • compress-loop 调用经 buildForwardHeaders(headers) 复用 buildForwardTarget 的 headers(src/server.ts:2060-2068、src/server.ts:2110-2115),继承主转发的标记 —— 与 PR 描述一致。
  3. 无头丢失/重复风险:x-bili-hop 不在 UPSTREAM_HOP_HEADERS(src/server.ts:127-146)中,passthrough 时不会被剥离;Node 对入站 header key 统一小写,打标记写 headers["x-bili-hop"] 不会与拷贝入站头产生大小写重复。
  4. 自环检测:hopMarker === instanceId 区分外部串联与自环,instanceId 为 per-server randomUUID()(同进程双 server 的测试场景可区分,重启换 id 无害)。
  5. 单实例无误报:客户端从不发送该头;claude 回带 acp 标签的消息不带此头,正常回合不受影响。
  6. 测试质量:两个用例断言到位 —— 用例 1 验证 body 逐字节一致 + 恰好一条 [chain] 告警点名上游标记 + 标记未被 B 重打;用例 2 验证 A→B 全链路仅 1 次 LLM 请求、LLM 处标记是 A 的且与 B 告警中点名的一致。

独立复跑 pre-flight(本地 checkout 分支后执行):

  • npm run typecheck ✅ 无错误
  • npm test ✅ 693 pass / 0 fail(691 既有 + 2 新增,与声明一致)
  • npm run build ✅ 成功

对照 #300 验收标准:串联拓扑单层处理 ✅;单实例使用完全不受影响 ✅;修复要求 1/2/3(出站标记、入站跳过+warn、e2e 测试)全部满足。

一个非阻塞观察:客户端若刻意发送 x-bili-hop 可让 bili 跳过对其流量的处理 —— 但这只是它自己的流量(想绕过压缩可直接连 LLM),不构成安全问题,且与 PR 中记录的"客户端从不发送该头"设计前提一致。

merge 后 CI 会自动发布(内容分支,无 version bump,走常规 release 流程)。

@ranxianglei

Copy link
Copy Markdown
Owner Author

Model: glm-5.3

Reviewed (supervisor pass): per-server instanceId, inbound hop marker → [warn] (self-loop distinguished) + full passthrough, stamping only on requests this instance actually prepares (marker keeps propagating through passthrough). e2e chain test covers both single B and full A→B chain. Matches the acceptance criteria from issue #299. Good to merge.

@ranxianglei
ranxianglei merged commit db29aca into master Aug 28, 2026
6 checks passed
@ranxianglei
ranxianglei deleted the 2026-08-28_bili-hop-marker branch October 15, 2026 05:32
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.

fix: stitched-stream usage double-count after compress re-request (adapter-anthropic >0 guard)

1 participant