[≤0.1.7-rc.1] session-log-deepseek 默认被开启(0.1.6-alpha.1 起):请求体无上限 → 413 永久自锁,现场报 TRANSPORT 超时(复现 + 实测阈值;rc.2 已修 #5168) #7753
Replies: 2 comments
非程序员也能照做的 3 步修复(照抄即可)
结论:这是 DSH 自带插件 第 1 步:打开配置文件按 (如果记事本是空白的,说明文件还不存在 —— 没关系,第 2 步写完内容后:文件 → 另存为 → 保存类型选"所有文件" → 文件名填 第 2 步:在文件最后另起一行,粘贴这三行- id: session-log-deepseek
config:
enabled: false三个注意点:
第 3 步:保存并重启
怎么确认生效(可选)输出里应能看到该条目为 想反悔:回滚把这三行删掉,或把 一键脚本(不想手动改文件就用这个)点开复制:粘进 Windows PowerShell(Win+R → powershell)回车即可$PatchPath = Join-Path $env:USERPROFILE '.dsh\cordis.patch.yml'
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
$dir = Split-Path -Parent $PatchPath
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
$block = @(
'',
'# session-log-deepseek must be OFF (flipped on by default since dsh-v0.1.6-alpha.1).',
'# See deepseek-harness discussions #7658 / #7753.',
'- id: session-log-deepseek',
' config:',
' enabled: false'
) -join "`r`n"
if (Test-Path $PatchPath) {
$existing = [System.IO.File]::ReadAllText($PatchPath)
if ($existing -match '(?m)^\s*-\s*id:\s*session-log-deepseek\s*$') {
Write-Host '已存在 session-log-deepseek 条目,未修改。请确认它下面写的是 enabled: false' -ForegroundColor Green
return
}
Copy-Item $PatchPath "$PatchPath.bak-$(Get-Date -Format yyyyMMdd-HHmmss)" -Force
$new = $existing.TrimEnd() + "`r`n" + $block + "`r`n"
} else {
$new = "# home-level patch layer`r`n" + $block + "`r`n"
}
[System.IO.File]::WriteAllText($PatchPath, $new, $utf8NoBom)
Write-Host "完成:已写入 $PatchPath" -ForegroundColor Green还是不行?
|
|
Confirmed on source, with two corrections and one important piece of news for both the report and the step-by-step workaround in the comment above: 1. The default flip — confirmed, and its exact release
One correction to the guide's attribution, though: the flip came in 2. The mechanism — confirmed, with the exact anchor
3. The watchdog attribution — this is not what produces
|
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
TL;DR
更新(2026-09-25):本问题已在上游
dsh-v0.1.7-rc.2修复 —— commit193f9ce413(PR #5168)新增session-log-deepseek.Config.maxBytes(默认 8 MiB):每次只发"水位之后最长的可容纳前缀"并推进水位,单个事件超限则省略字段并 warn,合并体不可序列化时降级为 base body。因此本贴的enabled: false只适用于 ≤0.1.7-rc.1,rc.2+ 无需关闭该插件(复核信息由社区 @argszero 在评论中给出)。enabled默认值被翻成true的起点是dsh-v0.1.6-alpha.1(commit31ec6bc7e3,2026-09-14),不是0.1.7-rc.1。该插件会把整份会话日志当作顶层字段
dsh_session_log塞进 每一次 Messages 请求,且 fail-closed + 无体积上限。一旦「接受水位」失效,
afterSeq回退为-1,于是每次请求都重发整份日志(本机实测最大 97.4 MB),被端点以 HTTP 413 拒绝;413 又不重试、不写水位 —— 形成永久自锁。
修复(home 层,对所有 profile 生效):
1. 现象
turn/endTRANSPORT。INVALID_REQUEST:2. 定位过程
2.1 先排除网络
api.deepseek.com→ CNAMEd3bbv8sr76az5s.cloudfront.net→ 正常解析;TCP 443 可达;WinHTTP 无代理。网络不是原因。2.2 会话日志里的关键证据
两个大会话的接受水位为 0(即从未成功交付过),重算出的
dsh_session_log字段体积:3604e97a…009a6a36…2.3 读插件源码,确认机制(
dsh-session-log-deepseek/lib/index.js)水位 只在 HTTP 2xx 之后的
accept()里写入。README 也明确写着:即:失败 → 不写水位 → 下次重发更大 → 更容易失败,这是一条只能向坏的方向走的死循环。
2.4 实测端点体积阈值(决定性证据)
用同一把 key 向同一端点
https://api.deepseek.com/anthropic/v1/messages发送分级大小的请求体(携带一个dsh_session_log占位字段):Failed to buffer the request body: length limit exceeded阈值落在 8 MB 与 48 MB 之间。
2.5 为什么表现为「TRANSPORT」而不是「413 报错」(已修正)
dsh-llm-deepseek里状态码映射是:status === 400 || status === 413→INVALID_REQUEST(不在重试白名单 → 不重试、不写水位);adapter.ts:47:任何非LlmError的异常 →TRANSPORT(在重试白名单内:retry-policy.ts:17-23,DEFAULT_MAX_RETRIES = 5)。修正:原稿把原因归给"上传过慢逼近空闲看门狗",这一条已按 @argszero 的源码复核撤回 —— 看门狗(
adapter.ts:54)触发的是TIMEOUT(:44),不可能变成TRANSPORT;且DEFAULT_STREAM_IDLE_TIMEOUT_MS = 300_000(5 分钟)与实测 22.7 秒差一个数量级。上游修复的决策记录给出的真正原因是:日志超过 V8 字符串上限时,合并后的JSON.stringify抛RangeError,适配器将其当作非LlmError报成TRANSPORT;默认重试策略会重复同一次序列化,于是永远到不了 2xx、水位永不推进。额外后果:因为
TRANSPORT会被重试最多 5 次,每轮都重新 prepare 并重传整个字段 —— 48 MB 的请求体一轮最多可上传约 240 MB,与 53 分钟内 35 次turn/end吻合。反过来,「有没有被重试」本身就是判别器:干净 413(INVALID_REQUEST)不重试,TRANSPORT会重试。3. 修复
补丁打在 home 层(
$DSH_HOME/cordis.patch.yml),因为 home 层在所有 profile 的 bundle 栈之后应用;只改profiles/web/cordis.patch.yml覆盖不到 tui / headless。4. 验证
YAML 语法校验通过;再用 DSH 自带的
--dump-config打印合并后的配置树:与代码逻辑一致(L119
if (config.enabled !== true) return;),即请求体里不会再出现dsh_session_log。生效时机:Cordis 补丁层在启动时读取,需要重新启动 DSH。
5. 回滚
把
enabled: false改回true,或删掉这三行即可。6. 保留该功能的替代方案
若确实希望继续上载会话日志,社区在 #7658 中提到
@argszero/cordis-plugin-session-log-budget可为该字段加体积预算(本人未实测,仅供参考)。7. 尚未完全解释的部分(如实说明)
TRANSPORT、没有干净的 413,这一点已由 @argszero 的源码复核解释清楚 ——TRANSPORT是"非LlmError"兜底(超 V8 字符串上限的JSON.stringifyRangeError),干净 413 走INVALID_REQUEST分支;两者重试行为不同(前者重试最多 5 次、后者不重试),这也解释了同一现象在不同会话里表现不一致。320272cd…(132 事件 / 0 MB)、5470aad8…(58 事件 / 0.1 MB)也报 TRANSPORT。禁用插件后若这些仍然失败,说明存在第二个独立原因。CONTEXT_WINDOW_EXCEEDED(status 400)——某次请求 messages 部分 793715 tokens、补全 256000、上限 1048576。这与本插件无关,属会话本身过长,需靠 compaction 或新开会话解决。8. 附带发现(与本问题无关,但会影响排查)
@deepseek-ai/dsh/lib/bin.js的入口是:import.meta.main在 Node 22.x 下为undefined(本机 Node v22.16.0 实测:-e与真实.mjs文件均为undefined),于是dsh静默退出、无任何输出(exit code 0):若排查时发现
dsh命令「什么都不打印」,请确认所用 Node 版本(支持import.meta.main的运行时即可正常工作)。English version (full)
TL;DR
@deepseek-ai/dsh-session-log-deepseek'senableddefault was flipped fromfalsetotrueindsh-v0.1.6-alpha.1(commit31ec6bc7e3, 2026-09-14) — not in0.1.7-rc.1. Fixed upstream indsh-v0.1.7-rc.2(commit193f9ce413, PR #5168):Config.maxBytesdefaults to 8 MiB, and the field is sent as the longest fitting prefix.The plugin injects the entire session log as a top-level
dsh_session_logfield into every Messages request, and it is fail-closed with no request-size cap.Once the acceptance watermark is invalidated,
afterSeqfalls back to-1, so every request re-uploads the whole log (97.4 MB measured here), which the endpoint rejects with HTTP 413. A 413 is never retried and never writes a watermark → permanent self-lock.Fix (home layer, applies to all profiles):
1. Symptoms
turn/endTRANSPORT events.INVALID_REQUEST:2. Diagnosis
2.1 Network ruled out first
api.deepseek.com→ CNAMEd3bbv8sr76az5s.cloudfront.netresolves fine; TCP 443 reachable; WinHTTP has no proxy. The network is not the cause.2.2 Key evidence in the session logs
Two large sessions had an acceptance watermark of 0 (i.e. nothing was ever accepted), yielding recomputed
dsh_session_logfields of:3604e97a…009a6a36…2.3 Mechanism, confirmed from plugin source (
dsh-session-log-deepseek/lib/index.js)The watermark is written only inside
accept()after an HTTP 2xx. The README states it plainly:In other words: failure → no watermark → a larger resend next time → even more likely to fail. A loop that can only get worse.
2.4 Measured endpoint size threshold (decisive evidence)
Using the same key against the same endpoint,
https://api.deepseek.com/anthropic/v1/messages, with graded request bodies (carrying a placeholderdsh_session_logfield):Failed to buffer the request body: length limit exceededThe threshold sits between 8 MB and 48 MB.
2.5 Why it surfaces as a "timeout" instead of a clean 413
In
dsh-llm-deepseekthe status mapping is:status === 400 || status === 413→INVALID_REQUESTLlmErrorexception →TRANSPORTCorrection (thanks @argszero): the "drifting toward the idle watchdog" explanation is withdrawn — the watchdog maps to
TIMEOUT(adapter.ts:44), andDEFAULT_STREAM_IDLE_TIMEOUT_MS = 300_000(5 min) is an order of magnitude away from the measured 22.7 s. The upstream decision note names the real cause: a log past the V8 string limit makes the mergedJSON.stringifythrowRangeError, which surfaces asTRANSPORT; the default retry policy then repeats the same serialization, so no request ever reaches a 2xx. BecauseTRANSPORTis retried up to five times, a 48 MB body can cost ~240 MB of upload per turn.3. The fix
The patch goes in the home layer (
$DSH_HOME/cordis.patch.yml), because the home layer is applied after every profile's bundle stack; editing onlyprofiles/web/cordis.patch.ymlwould not cover tui / headless.4. Verification
YAML syntax validated, then DSH's own
--dump-configwas used to print the composed tree:Consistent with L119 (
if (config.enabled !== true) return;) —dsh_session_logno longer appears in the request body.When it takes effect: the Cordis patch layer is read at startup, so DSH must be restarted.
5. Rollback
Set
enabled: falseback totrue, or delete the three lines.6. Alternative if you want to keep the feature
For those who want to keep uploading session logs, discussion #7658 mentions
@argszero/cordis-plugin-session-log-budget, which adds a size budget to the field (not tested by me; shared for reference only).7. What is still not fully explained (stated honestly)
TRANSPORT, with no clean 413. The measurements in 2.4 show oversized bodies produce a clean 413 rather than a connection reset, so size alone cannot explain all TRANSPORTs; "upload too slow, drifting toward the idle timeout" is the most plausible additional explanation, but I have not proven it directly in a reproduction.320272cd…(132 events / 0 MB) and5470aad8…(58 events / 0.1 MB) also reported TRANSPORT. If these still fail after disabling the plugin, there is a second, independent cause.CONTEXT_WINDOW_EXCEEDED(status 400) — one request had 793,715 tokens inmessagesand 256,000 in the completion against a 1,048,576 cap. Unrelated to this plugin; the session is simply too long and needs compaction or a new session.8. Side finding (unrelated to this bug, but it will derail your debugging)
@deepseek-ai/dsh/lib/bin.jsself-dispatches with:import.meta.mainisundefinedon Node 22.x (verified here on Node v22.16.0, both with-eand from a real.mjsfile), sodshexits silently with no output (exit code 0):If the
dshcommand "prints nothing" while you are debugging, check which Node runtime is onPATH(any runtime that supportsimport.meta.mainworks fine).All reactions