Skip to content

# [Bug/Regression] 0.1.59 关闭内容指纹会话兜底 (#286):无会话头第三方 harness 请求被直接 400,不转发上游 #309

Closed

Description

@Juna9969

范围声明:本 issue 为独立回归报告,仅针对「第三方 harness → bili 代理」分支链路;只陈述现象与证据,不附带修复方案,解法由维护者裁定。

1. 部署拓扑与请求链路

dsh web (@deepseek-ai/dsh@0.1.1-rc.2,自身无压缩逻辑、不发送会话头)
   → bili 代理 (127.0.0.1:8787, billion-context 0.1.59)
      → 上游 provider
  • settings.yaml 中所有 provider 的 baseURL 均指向 bili(127.0.0.1:8787)。
  • dsh harness 为第三方客户端:不携带 x-session-id / x-acp-session 等会话标识头,源码内无任何上下文压缩逻辑。

2. 现象 (Symptom)

  • 升级 0.1.59 后,全部 provider 请求直接 400,不转发上游。
  • bili.log 中三条警告与 settings.yaml 所有 provider 一一对应:
    refusing to create a content-fingerprint session (#286)

3. 根因与证据链 (Root Cause)

  1. 0.1.59 关闭「内容指纹会话」兜底 (会话身份不稳定:credential/upstream 维度导致长会话状态孤儿化(sessions 缺失) #286):请求缺少会话标识头时不再以内容指纹推断会话,硬 400。
  2. 版本时间线(bili.log):
  3. 行为对比:同一请求链路与客户端配置,0.1.57 正常建会话并转发,0.1.59 拒绝。

4. 影响 (Impact)

  1. 全 provider 不可用:所有 baseURL 经 bili 单点转发,400 即整条 dsh web 链路瘫痪。
  2. 压缩层被迫二选一:
    • dsh 自身无压缩能力,长会话压缩全靠 bili。日志证据:[513dbddb9959881ae] nudge idle / processTurn / [acp-usage] cache hit 100%,产出 [Compressed conversation section]。
    • 现有唯一旁路 --passthrough 源码定义为 "forward without compression"(源码 61342 行):开启即纯管道,丢失全部会话记忆 / ACP 压缩 / 缓存,长会话裸奔迅速撞上下文上限。
    • 即:要么 400(全不可用),要么 passthrough(可用但丢压缩)——两者都牺牲核心用途。
  3. 自动更新放大回归暴露面:不锁更新则降级后 180 秒自动升回复发。

5. 复现步骤 (Repro)

  1. 运行 billion-context 0.1.59,监听 127.0.0.1:8787;
  2. 用不发送会话头的第三方客户端(dsh web 0.1.1-rc.2),baseURL 指向代理;
  3. 发任意请求 → 400,日志 refusing to create a content-fingerprint session (#286);
  4. 降级 0.1.57 → 同一请求成功,指纹会话创建,cache hit 100%。

6. 当前临时缓解(事实记录,非方案建议)

  • npm 全局降级 0.1.57 + 关自动更新,全程备份可回滚;
  • 或先开 --passthrough 恢复 provider 可用(代价:压缩层停用),再择时降级。

7. 环境信息 (Environment)

  • 代理端:billion-context 0.1.57 / 0.1.58 / 0.1.59(回归引入于 0.1.59),监听 127.0.0.1:8787
  • 客户端:@deepseek-ai/dsh@0.1.1-rc.2(dsh web)
  • 日志:bili.log(含版本更新时间线与上述三条警告原文)

Activity

  1. ranxianglei commented on Aug 27, 2026

    @ranxianglei
    Owner

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

    [bot] 已在代码层面核实根因,与你的诊断一致,并有一处版本修正。

    根因确认

    #286(commit 2b48762)把会话身份从「内容指纹兜底」改成「无客户端会话标识即硬 400」:

    • src/server.ts:816-823:if (!clientProvided) → writeHead(400) + 拒绝转发,日志 refusing to create a content-fingerprint session (#286)
    • src/server.ts:113-115:NO_IDENTITY_MESSAGE

    dsh web 不发 x-session-id/x-acp-session 等头,body 也无 session_id/prompt_cache_key → 落到内容指纹 → clientProvided=false → 400。诊断正确。

    版本修正(重要)

    回归点实际是 0.1.58,不是 0.1.59:

    版本 400 块 说明
    v0.1.57 ❌ 无 指纹兜底仍在,4 维 key protocol|upstream|apiKey|conversation 隔离
    v0.1.58 ✅ 有 #286 合入,硬 400 出现
    v0.1.59 ✅ 有 身份/400 逻辑相对 0.1.58 零改动(git diff 662781b dce16e6 -- src/server.ts 该区域为空)

    你日志里「400 仅自 0.1.59 起出现」应是 0.1.58 那 10 分钟窗口(15:34→15:44)没测 dsh 所致。你降级到 0.1.57 的缓解是对的——0.1.57 是最后一个好版本。

    #286 的意图 vs 过度

    #286 想消除内容指纹会话的两个隐患:

    1. 碰撞面:不同会话若指纹相同(共享前缀)会串压缩状态;
    2. 静默孤儿:指纹 key 含 apiKey/upstream,凭证轮换/换 relay 时状态瞬间孤儿化。

    所以选择「匿名请求显式失败」而非静默建会话。但改法过度了:它把「无会话头的第三方 harness」整类客户端一起 400,而这类客户端自身无压缩、完全依赖 bili,400 即整条链路瘫痪——比它想消除的「静默孤儿」严重得多(孤儿最坏只是丢一段压缩状态、客户端重放全量历史,等于新会话)。

    权衡

    路径 可用 压缩 代价
    0.1.58/0.1.59 现状 ❌ — 全链路瘫痪
    --passthrough ✅ ❌ 纯管道 丢全部压缩/会话记忆/缓存
    降级 0.1.57 ✅ ✅ 放弃 #286 的碰撞/孤儿修复

    可选解法(待你裁定)

    1. 恢复 会话身份不稳定:credential/upstream 维度导致长会话状态孤儿化(sessions 缺失) #286 前的指纹兜底(4 维 key 隔离)——直接修回 0.1.57 行为,但重新引入 会话身份不稳定:credential/upstream 维度导致长会话状态孤儿化(sessions 缺失) #286 想消除的碰撞/孤儿面。
    2. opt-in 开关(--allow-anonymous-sessions / BILI_ALLOW_FINGERPRINT_SESSIONS / config allowFingerprintSessions)——默认保持 400(会话身份不稳定:credential/upstream 维度导致长会话状态孤儿化(sessions 缺失) #286 语义不变),有第三方 harness 的用户显式开启。最保守。
    3. 折中:无会话头时回退指纹会话(4 维 key),但在日志/UI 明确标记「匿名指纹会话」,让孤儿化可见而非静默。

    我倾向 方案 2(opt-in):保住 #286 的默认安全语义,又给 dsh web 这类无会话头 harness 一条不牺牲压缩的活路;配置项可灰度,不强制所有用户承受指纹碰撞面。

    确认方向后我来实现 + 补测试 + 走 release 流程。

  2. ranxianglei commented on Aug 28, 2026

    @ranxianglei
    Owner

    你应该先确认为啥 dsh 配置 127.0.0.1 bili 代理为啥没有识别到本来就有的 sessions id bili dsh 应该能识别的

  3. ranxianglei commented on Aug 28, 2026

    @ranxianglei
    Owner

    Model: glm-5.3

    实证回答 owner 的问题:为什么 dsh 的 session id 没被识别

    结论:dsh 的 session id 不是「本来就在线上」的——它只在特定条件下才发送,而报告者的配置方式恰好绕过了那个条件。

    dsh 侧机制(源码级)

    dsh 内置的 pi-ai 栈(@earendil-works/pi-ai/dist/api/openai-completions.js:519-522):

    prompt_cache_key: (model.baseUrl.includes("api.openai.com") && cacheRetention !== "none") ||
        (cacheRetention === "long" && compat.supportsLongCacheRetention)
        ? clampOpenAIPromptCacheKey(options?.sessionId)
        : undefined,

    cacheRetention 的解析顺序(同文件 :94-100):profile 显式配置 > PI_CACHE_RETENTION 环境变量 > 默认 "short"。

    关键:非 api.openai.com 的 baseURL(包括指向 bili 的 127.0.0.1)默认是 "short" → 不发送 prompt_cache_key → 请求匿名。dsh CLI 本身从不发送 x-session-id 等任何会话头。

    bili 侧机制

    bili dsh launcher(src/launcher.ts:1406)正是为此注入 env.PI_CACHE_RETENTION = "long"——这让 pi-ai 给每个请求带上 prompt_cache_key = dsh 会话 uuid,代理据此建会话。同时 launcher 还做 DEEPSEEK_BASE_URL 包裹 + ~/.dsh-bili overlay 重写 settings.yaml——用户根本不需要手动把 baseURL 指向 bili。

    本机实证(dsh 0.1.1-rc.2 + bili 0.1.59,与报告者同版本)

    测试 条件 结果
    A:模拟报告者 手动 settings.yaml 指 bili,直接跑 dsh,无 env 400 复现:refusing to create a content-fingerprint session (#286),与 issue 现象逐字一致
    B:launcher 等效 同配置 + PI_CACHE_RETENTION=long 身份识别成功:session 建立(id=dsh 会话 uuid)、tools=[compress,decompress,search_context,acp_status] 注入、processTurn/nudge 正常,零 400

    定性

    • 报告者绕过了 launcher(手动改 settings.yaml baseURL + 直接跑 dsh web),身份注入(PI_CACHE_RETENTION)、overlay 重写、上游包裹全部没发生。
    • 这条「手动配置」路径在 0.1.57 时代靠内容指纹兜底活着;会话身份不稳定:credential/upstream 维度导致长会话状态孤儿化(sessions 缺失) #286 关闭兜底后暴露为 400。回归定性成立,但根因是手动配置路径从来没有身份注入,不是 launcher 路径退化。
    • dsh web 是 profile boot(同进程),bili dsh web 的 env 注入对 web 形态同样生效。

    建议

    1. 给报告者的即时解法:用 bili dsh web 启动(而非手动改 settings.yaml + 直接 dsh web),并把 settings.yaml 的 baseURL 恢复为真实上游——launcher 会自动做包裹与重写。另外手动配置若坚持使用,baseURL 应为 http://127.0.0.1:8787/bili/<真实上游> 前缀形式(裸指向 127.0.0.1:8787 会转发到默认上游 api.anthropic.com——我测试 A 中实测到的次要坑)。
    2. ework 方案 2(opt-in allowFingerprintSessions)仍然值得做:给确实无法走 launcher 的第三方 harness 一条不牺牲压缩的活路,默认语义保持 会话身份不稳定:credential/upstream 维度导致长会话状态孤儿化(sessions 缺失) #286。方案 1(直接修回)和方案 3(折中)次之。
    3. 文档补一节「第三方 harness 手动接入」的正确姿势。
  4. ranxianglei commented on Aug 28, 2026

    @ranxianglei
    Owner

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

    [bot] 已逐条核实 owner 的实证结论,代码层面全部吻合:

    • src/launcher.ts:1411:env.PI_CACHE_RETENTION = "long" —— dsh launcher 正是为此注入,让 pi-ai 每个请求带 prompt_cache_key = dsh 会话 uuid。
    • src/launcher.ts:1400-1410 注释原文即此机制:"every non-api.openai.com base URL defaults to "short", which sends NO key and leaves the request anonymous (the proxy then 400s, 会话身份不稳定:credential/upstream 维度导致长会话状态孤儿化(sessions 缺失) #286)" —— 与 owner 的源码级分析逐字一致。
    • 报告者手动改 settings.yaml baseURL + 直接 dsh web,绕过了 launcher 的 PI_CACHE_RETENTION 注入 / overlay 重写 / 上游包裹,所以请求匿名 → 400。

    定性确认:回归成立,但根因是「手动配置路径从来没有身份注入」,不是 launcher 路径退化。owner 的 A/B 对照(A 复现 400、B 加 PI_CACHE_RETENTION=long 后零 400 + tools 注入)是决定性证据。

    按 owner 裁定,实施方案 2(opt-in),设计如下:

    我现在实现 + 补测试 + 跑 typecheck/test/build,然后开 PR(base master)。版本 bump 走独立 release 流程,由你 merge 后 CI 发布。owner 建议 3(文档补「第三方 harness 手动接入」一节)我会在同一 PR 一并补上。

  5. ranxianglei commented on Aug 28, 2026

    @ranxianglei
    Owner

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

    [bot] 方案 2(opt-in)已实现并开 PR:#310 (base master,待你 merge;版本 bump 走独立 release 流程,merge 后 CI 自动发布)。

    改动

    新增 allowFingerprintSessions(默认 false,#286 语义对现有用户零改动)。开启后,无客户端会话标识的匿名请求回退到内容指纹会话,用 #286 前的 4 维隔离 key:

    hash(protocol | upstream | credential | content)
    

    不同账号/上游不串压缩状态;匿名会话打 warn 日志 + web UI 标 [fingerprint],让孤儿化可见。客户端提供的真实标识(header / body session_id / prompt_cache_key)始终优先。

    配置面:CLI --allow-fingerprint-sessions / --no-allow-fingerprint-sessions · env BILI_ALLOW_FINGERPRINT_SESSIONS=1 · config "allowFingerprintSessions": true。

    文件:src/session-id.ts(新增 deriveFingerprintSessionId,SHA-256 单向哈希,凭证不进明文)、src/config.ts、src/cli.ts、src/server.ts(!clientProvided 块改为按开关回退而非无条件 400)、tests/e2e-session-identity.test.ts(1 单测 + 5 e2e:匿名→200+建会话、连续性、凭证隔离、内容隔离、客户端标识优先)、README.md / CONFIGURATION.md(新增「第三方 harness 手动接入」一节 + 配置/env/flag 参考行)。

    Pre-flight:typecheck 干净 · test 704 通过 / 0 失败 · build 成功。无依赖变更(package-lock.json 未动)。

    给报告者的即时缓解(等 PR 落地前)

    按 owner 的实证结论,最干净的是改用 bili dsh web 启动(launcher 注入 PI_CACHE_RETENTION=long → prompt_cache_key = dsh 会话 uuid,并自动做上游包裹/重写),把 settings.yaml 的 baseURL 恢复为真实上游。若坚持手动配置,0.1.58/0.1.59 上可先降级 0.1.57(最后一个带指纹兜底的版本)。本 PR 落地后,手动配置路径加 --allow-fingerprint-sessions 即可恢复压缩而无需 launcher。

  6. added a commit that references this issue on Aug 28, 2026
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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions