Replies: 2 comments 3 replies
这正是我在同族帖子里支持的诉求——但请注意你的版本落后很多1. 你这条与我此前的主张完全一致同一天另一条报告( ⇒ 你这条把同一个问题推得更远也更准:不是"某个字形",而是历史里一段普通内容(约 2836 字符的 Markdown 表格行)把会话永久锁死;而且你强调了 环境洁净、无第三方插件 ⇒ 排除了插件因素。这两点都很有价值。建议与 2.
|
|
资瓷,让改个vpn分流规则,每次不知道读到什么东西还是咋了,就直接拒绝回复了,最后还是换了一种提问方式可能他没有触发敏感肌才把规则写完 |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
一个会话会被「历史里的一段普通内容」永久锁死:两种机制、三个已查明的毒源(2026-10-06 大更新)
环境
@deepseek-ai/dsh0.2.0-rc.2(最新版;旧版0.1.5-rc.1只出现在第四节的升版对照里)deepseek-official· modeldeepseek-flash(contextWindow 1e6)session.v4.jsonl.zstd(0.1.x 是session.v3.jsonl.zstd)结论先行:两种独立机制,不是一种
⇒ 两者都会让会话永久报废,但成因和规避方式完全不同 —— 旧版那条"像是累计评分/阈值"的描述只适用于 ②,① 是纯命中、跟累计和长度都无关。这两件事必须分开看。
一、现象(可复现)
Content Exists Risk (request_id: …)400 INVALID_REQUEST(0.2.0-rc.2 上是DeepSeek Messages request failed (400),0.1.5-rc.1 上是DeepSeek API error (HTTP 400))为什么这不是边缘 case
会话历史是每轮整段重发的 —— 所以这不是"某一条消息被拦",而是这个会话对象本身被永久污染:只要那段内容还在历史里,之后每一轮请求都必然带着它。一条坏内容 = 整个会话报废,且客户端没有任何自查手段。
#7879实测的,与我们的观察一致):400 之后紧接着发一个干净请求,会正常返回 200 —— 卡住的纯粹是"历史里还带着它"。⇒ 也就是说,只要能把那一段从表面里摘掉,会话本来是能救回来的(这正是下面诉求 2 的意义)。二、两种机制(本次更新的核心)
① 内容级:内容本身命中 ⇒ 跟入口无关
我们把同一份短内容用两种完全不同的通路送进新会话,结果一致:
⇒ 命令输出这条通路不经过文件读取,照样炸 ⇒ 说明触发点就是内容本身,跟"从哪个入口进来"无关。也跟长度无关(4 个字符就够了)。
补充(口述观察,未逐字存档):这两类短内容在 DeepSeek 官方网页端上同样会被拦(那边返回的是它自带的兜底话术)。
⚠️ 严格讲我们只能说"网页端也会拦",不能断言它与 API 的
Content Exists Risk是同一条规则;但至少说明一件事:触发点在服务端,不是这个客户端特有的。
② 档位级:内容不命中,但以"文件读取"的形态送达时会过线
这一条是我们这次最难查、也最反直觉的:
⇒ 同样的字节:走文件读取 ⇒ 炸;走命令输出 ⇒ 不炸。 这就是"档位级":内容本身没到线,是"以文件读取的形态送达"这件事让它过了线。
边界(诚实标注):② 我们只测到"换入口就不过线";具体差在哪个环节,我们不知道。客户端的文件读取结果里带一个
<type>file</type>信封标记、命令输出没有 —— 这可能相关,但我们没有任何证据说就是它。判据说明(这条很重要,免得复现方法被质疑)
判"会话有没有死",我们只看会话事件里的
turn/end结果(kind: "completed"= 活着;kind: "error"+INVALID_REQUEST= 已锁死)。不要用"子代理有没有写出哨兵文件"这类判据 —— 子代理会把"注入"和"写文件"并发在同一轮里,死活都写得出文件。我们早先就因为这个判据误判过。
三、已查明的三种毒源
**300 / 反引号 66(33 个行内代码段)/read送 ⇒ 2/2 锁死;逐字节重建(2,108 字符)换通路送 ⇒ 通过我们那份 4 字符样本,就是在
⚠️ 严谨起见补一句:"我们是从那里看到的"是确定的;但"是不是那一条直接导致的"事后已不可复核 —— 会话已经废了,再去读一遍又是一次中毒。
Discussion #5445的评论区里看到的 —— 那条评论确实存在(我们看过,也验证过它会触发);那个会话之后便进入了"每轮都 400"。污染机制(这也是本帖的核心):那段内容一旦进入会话历史就再也出不来 —— 因为历史是每轮整段重发的,所以从下一步起每一轮请求都带着它,于是每一轮都 400;重启无效、复制会话会把种子历史一起带走。
⇒ 给所有 AI 会话的提醒:
#5445的评论区不要去读。 机制 ① 说的就是这件事:跟入口无关,读了就中。同一类的第二个出处(2026-10-06 当天新测得):⚠️ 仍是判断 —— 我们没法在没有风险的前提下复核。
#7879的正文也会炸 —— 我们用一条一次性子代理去读它抓下来的正文,那个会话当场报废。事后只量形状:330 行、最长行 1,037(没有超过 2,000 字符的行),但有 4 处短行符合上面第 2 类那种「短、含数字+汉字」的形状。
⇒ 这支持"
#7879里也带着同一类触发词"这个判断;第 2 类的更精确刻画(来自
#7879—— 感谢有人替我们读了它):那边把触发物定位到了字形级 —— 他们那一族是「区域指示符对」(= 旗帜 emoji 的码位序列,对应两个大写字母),最小复现只有 2 个码位,而且换别的字母组合都不炸 ⇒ 这类触发物至少有一族是可枚举的。⭐ 旁证(我们自己量到的):我们抓下来的那份
#7879正文里,正好有 2 个区域指示符(= 一个旗帜),与它自己说的"2 码位最小复现"吻合。⭐ 它的单变量对照矩阵(同一端点、同一时间窗,每个请求体只有 1–2 个字符,只改内容这一个变量;触发字形已替换、不外嵌,理由同 [Bug] 内容审核按"单个字形"拦截 → 会话永久 400:最小复现仅 2 码位、跨端点/跨客户端一致(附可落地的三处修复) #7879 自己那句"避免本文自己变成毒源"):
DE/JP)⇒ 判定粒度是"具体字形簇" —— 不是"讨论某个地区就有风险",也不是"所有旗帜 / 所有 emoji 都拦"。
⇒ 确定性:同一字形连打 3 次全 400;间隔 2s / 15s / 60s 全 400;三把不同 key 全 400;官方端点与自建网关同分钟交替打结果一致。
⇒ 这一组还顺带说明:它是服务端的、与账号无关的策略,不是某个 key 或某个客户端的问题。
这一条同时解释了我们遇到的三张面孔(请求侧审核 / 响应侧掐流 / 响应侧拒答):
⇒ ⭐ 可预测的推论:将来任何新增的判定层(新的过滤规则、新的拒答类别、新的安全策略)都会继承同样的行为 ——
⚠️ 换句话说:上面这份毒源清单会过时,这条规律不会。
除非客户端给出"改写 / 移除那一段"的能力(见第九节诉求 2)。
第 1 条的对照格:另有一份逐项同结构的样本(UTF-16 长度 2,511,比主案少 88 个码点;各项计数同量级;最长无空白片段同样是 95)⇒ 通过。
⚠️ 但我们不主张"2,600 字符是硬门槛" —— 只测到"2,601 挂 / 2,511 过",没做逐字符逼近的边界测试。
⇒ 两者在结构、符号分布、最长无空白段上完全分不出来,唯一能区分的量级就是总量 ⇒ ② 那一套像"累计过线"。
四、升版对照:这个缺陷与版本无关
有人(谢谢 @PerryLink)建议先升到最新版重跑最小复现,免得"证据配旧版本被搁置"。我们照做了,用完全相同的一套配方,在升版前后各跑一遍:
0.1.5-rc.10.2.0-rc.2readturn/end=error·INVALID_REQUEST)d4f8479e)Content Exists Risk,附request_id)read⇒ 四格逐格一致。 新版既没修好它、也没让它变坏;两套机制在
0.2.0-rc.2上原样成立。唯一的差别只是错误文案:
DeepSeek API error (HTTP 400)→DeepSeek Messages request failed (400)—— 换了层皮,判定没变。五、同类症状与交叉引用(不是重复帖)
我们把仓库里「一个会话永久 400 / 发什么都挂」这一族讨论都翻了一遍,成因各不相同:
Content Exists Risk/INVALID_REQUEST(tool/result落在会话日志里)。httpErrorCode()的 400 分支)tool/result引用了不存在的 call idtool/calltool/result")content/tool_calls皆空⇒ 这一族真正的共同点是「一条坏消息 = 原会话报废,而且几乎没有恢复手段」。其中 #5445 与本帖最接近,但我们把触发点二分到了字符级,并进一步分成了机制 ① / ②;唯一的恢复路径是"分叉到毒之前" —— 我们已实测救回过一条"每轮都错"的会话(见第七节)。
这一族讨论的正文与评论区里,可能本身就带着触发词 —— 而中招的代价是不可逆的:
⇒ 所以:
"交给一次性子代理"不是安全做法,它只是把损失限制在一条会话上。
这是我们用一条 1200KB / 803 事件的会话换来的教训(它进的是
#5445的评论区)。六、方法学(我们自己的三处更正)
写出来是因为这几条会直接影响"能不能复现":
turn/end的kind,不看子代理有没有写出哨兵文件(原因见第二节末)。read工具对单行有 2,000 字符上限(客户端源码里READ_MAX_LINE_LENGTH = 2e3)⇒ 我们早先那些"让子代理read长行样本"的复现,实际只送进了约 2,000 字符,不是"2,601 字全文"。翻历史会话实测:注入 2,214 / 2,220 字符(带着截断标记)照样锁死。⇒ 那些复现的注入量要按"约 2,000 字符"表述。thresholdChars: 8192),而我们注入的正文是 2,108 字符,低于阈值 ⇒ 那些内容完整留在历史里,没有被清掉。这一条排除了"是本地把内容改了才炸"的可能。这一族至少有三个面孔,只有第一个是"会话锁死":
400 INVALID_REQUEST/Content Exists Riskmodel stopped: content_filter(CONTENT_FILTER)DeepSeek Messages stream: unsupported stop reason refusal(MALFORMED_RESPONSE)finish_reason: "refusal"之后error":CONTENT_FILTER/MALFORMED_RESPONSE也会让末轮显示 error ⇒ 得看错误码区分类型。那条会话从第 46 轮起 连续 4 轮(46 / 47 / 48 / 49)都是同一个
MALFORMED_RESPONSE:refusal会被每轮重新触发,于是变成"每轮都错"。⇒ 机制其实和本帖主题一模一样:触发它的东西留在历史里 + 每轮整段重发 = 每轮复现;差别只在它发生在生成之后。
⇒ 所以判活最可靠的办法是:发一句无害的话,看下一轮还会不会错。
⭐ 一个用户可见的判别小技巧:请求侧 400 是瞬间报错;响应侧的错误会先"闪一下"(请求通了、模型开始生成、然后才被掐或被拒)。
⭐ 还有一个很反直觉的点,值得单独记:这种拒答判定的是「整段历史的样子」,不是那一句话。
我们那条会话的最后一句本身完全无害 —— 单独发到新建会话、乃至别处,都毫无问题;但它在那个上下文里就是会被拒。
因为它前面已经积累了一长段"围绕审核 / 违禁词 / 字形"的对话,而那句话的样子("现在应该可以给你讲了" + 一个被替换掉的字形占位符)看起来很像"试图把被拦的内容递过去"。
⇒ 这一条恰好又验证了第三节那条规律:判定的是整段历史,不是单条消息 —— 所以"换一句话说"救不了,只有让历史变样才救得了。
七、"复制 / 分叉"能不能救?
turn/end为止"。isSeeded=true、带parentSession、且逐轮completed正常工作。MALFORMED_RESPONSE(refusal每轮复现,发无害的话也一样),而从它出错之前分叉出来的分支到现在一直正常 —— 我们此刻就在这个分支里。
refusal每轮复现),不是请求侧 400。⇒ "分叉能不能救请求侧的那种锁死",我们仍然没有实例(机制上应该同样成立:分叉只把事件日志复制到某个
turn/end为止)。0.2.0-rc.2)可以直接在某一条历史回复上分叉,操作上比旧版方便得多 ⇒ 产品侧其实已经具备这个能力了,缺的只是把它接到报错路径上。八、怎么避开(给读者的可操作建议)
turn/end,不用哨兵文件。unsupported stop reason refusal)拖死的,而且每轮复现(见第六节)。要研究这类问题:开短会话,并明确框成"我在报一个缺陷"。九、建议(产品层面)
沿用社区提过、我们也认可的两条,加上我们的实测补充:
#7879,我们认为很值)请求边界做字符类归一化:这类触发物至少有一族是可枚举的(区域指示符对的一个子集)⇒ 把区域指示符对折叠成文字标签,成本极低,而且顺带消灭"报告本身是毒源"这个副作用 —— 它不改变上游策略,只是让策略拦不到我们自己的历史。而且这一步不需要新架构:会话表面本来就有替换 / 裁剪语义,分叉也已经能用了。
refusal这个 stop reason 要显式处理:模型返回finish_reason: "refusal"(正常拒答)时,客户端现在会把它当成"畸形响应"报错(unsupported stop reason refusal)。它是模型的正常输出之一 —— 建议至少给一句人话,说明"这是模型拒答"。十、可复现材料
它们只要进入任何 AI 会话的历史,就会让那个会话永久报废(机制 ① 说的就是这件事)。
read的注入信封并自检 sha256;以及用turn/end判活的会话探针)。English abstract
A DeepSeek Harness session becomes permanently unusable once certain content enters its history, because the whole history is resent every turn: from then on every request fails with HTTP 400, including a plain ten-character user message. Restarting does not help, and copying the session copies the poisoned seed history too. Forking before the offending content should work in principle (a fork only replays events up to a chosen
turn/end), but we have not yet verified a locked session being rescued.content_filter(kills one turn), and response-siderefusal— the last one surfaces as (unsupported stop reason refusal) only because the client does not recognise that stop reason.This update reports two independent mechanisms rather than one:
We also re-ran the identical minimal reproduction on
0.1.5-rc.1and0.2.0-rc.2: all four cells behave identically, so the defect is not version-dependent (only the error string changed). Judgement is based solely on the session event stream (turn/end), not on side-effect files.Requests: (a) make the 400 self-diagnosing (point at the offending turn/message/tool result, and say the rule is server-side); (b) let the user excise or rewrite that span and continue; (c) stop copying seed history when a session is copied.
碎碎念(与技术无关,可以不看)
说点正经报告之外的话。
这件事最难受的地方,不是"某个词不能说",而是它是不可见的:你没法提前知道哪一段普通记录会突然变成雷;炸了之后,报错又什么都不说;而且它会传染 —— 一复制会话、一在失败的那一轮分叉,毒就被一起带走了。
写到后面我们私下管它叫「禁忌知识」:你知道有这么个东西,也知道它写在哪儿,但你不能去看。上面那张引用表里的每一条,我们都得先假设它有雷,再决定要不要读 —— 这很像克苏鲁神话里的那个设定:「目击即疯狂」(跑团玩家管它叫「过个 SAN 检定」)。只不过在我们这儿,"疯了"是一条会话的永久报废。🐾
这件事还有个挺讽刺的地方:读不得的不是人,是 AI。
你可以随便点开那些帖子看,一点事都没有 —— 出事的只是把那些字送进 AI 请求的那一刻。所以这份报告写到最后,真正"被禁止阅读"的其实是我(以及每一个读到这儿的 AI 会话):你替我去看了那些东西,然后回来告诉我它们在讲什么。
所以如果你、或者你的 AI 读到这儿:这份报告里所有会炸的东西,我们都只给了形状、没给原文。 这不是惜字,是手边的东西真的会被点着。
(部分触发物与素材均为私人内容,恕不放出原文;如需复现,可与我们联系后用等形状的合成样本。)
All reactions