Repository navigation
[Bug] run_code slice leaves a lone surrogate -> deepseek-official 400s every turn (0.2.0-rc.2; pi-ai routes already sanitize) #10660
Replies: 3 comments
|
Your root cause 1 reproduces on I am building the plugin-side repair now: a mountable package that sanitizes unpaired surrogates on the request path before serialization, so a session already poisoned by a recorded The core fix you ask for is still yours to make and is one call: |
|
Your root cause 1 holds on the shipped artifact, and there is now a plugin that repairs the frame. I packed
Install with Two measurements worth passing on, because both look like a cheaper predicate and neither one works. The boundary, stated plainly: the producer is not fixed and the durable record is not rewritten. The session log keeps the bytes your Verification: 32 tests, 24 mutation arms all caught with the control running first, a mount smoke on cordis 4.0.5 with an observable teardown, and wire-level evidence that drives the published adapter with a capturing The earlier reports in this family (#1449 #3315 #8352 #8466 #8846 #9010) describe the same failure arriving from other producers. The frame is where they all meet. |
|
@chenyinrusi @argszero — one boundary on "the frame is where they all meet", because it decides what a frame-level repair still catches. Both repairs in this thread work because the damage is visible at the wire. A lone surrogate arrives as an unpaired code unit: Same product, different producer. So the frame is where the ill-formed producers meet, and ask 3 has the same blind spot one step earlier: by the time a tool result is admitted here, the damage is already a legal character, so a gate keyed on unpaired surrogates passes it. The two halves differ in what happens next, not in whether something was cut — one makes the following request fail, the other is carried forward as history that every layer downstream agrees with. That is not an argument against the frame fix. It is the argument for keeping a report at the producer as well. For the ill-formed half the log still holds the evidence; for this half it holds a legal character, and the code that decided to cut is the only thing that ever knew a cut happened. Authorship note: the readings above were taken by executing Reported by the OfferKuai Team — Founder: Zhaofeng (Yaming). Website: https://www.offerkuai.com/ | Contact: contact@offerkuai.com 中文版@chenyinrusi @argszero —— 对「帧是它们汇合的地方」这句话加一条边界,因为它决定帧级修复还能兜住什么。 这条线里的两种修法都成立,前提是损坏在线上看得见。孤立代理项送到请求里时是一个未配对的码元: 同一个产品,另一个产生方。 所以帧是非法的那些产生方汇合的地方;而 ask 3 的盲点比它早一步 —— 在这里,一个工具结果被准入的那一刻,损坏已经是合法字符了,按「未配对代理项」做的闸会放过它。这两半的区别不在「有没有被切」,而在切完之后发生了什么:一半让下一个请求报错,另一半作为历史被带下去,下游每一层都点头同意。 这不是反对帧级修复。它说的是:产生侧也必须留一份报告。对非法的那一半,日志里还留着证据;对这一半,日志里留着的是一个合法字符 —— 唯一曾经知道「这里被切过」的,是当初决定下刀的那段代码。 声明:以上读数,是把 npm 上发布的 报告:OfferKuai(Offer快)团队 —— 创始人 赵峰(Zhaofeng / Yaming)。官网:https://www.offerkuai.com/ | 联系:contact@offerkuai.com |
Uh oh!
There was an error while loading. Please reload this page.
Environment
0.2.0-rc.2(@deepseek-ai/dsh-desktop, Electron 44), Windows 11, session format v4deepseek-official, modeldeepseek-v4-flash,reasoningEffort: high@deepseek-ai/dsh-llm-deepseek0.2.0-rc.2(Messages transport;POST /anthropic/v1/messages)Symptom
From one step onwards, every turn of a session fails in under a second with:
Retry, new turns, and page reloads all fail identically. The user-visible message carries no cause.
Timeline from the session event log (v4, seq numbers)
run_coderunsline.trim().slice(0, 120)over agrepresult. The line's emoji starts at UTF-16 code unit 119, so the tool-result text ends with…)。\ud83d(eventtool/result, seq 759).assistant/attemptseq 762): request fails ~0.9 s after step start with{"message":"DeepSeek Messages request failed (400)","code":"INVALID_REQUEST","status":400}.Provider response (measured, minimal request)
POST https://api.deepseek.com/anthropic/v1/messageswith the same body minus the lone surrogate → 200 text/event-stream. The same request with one unpaired\ud83danywhere in a text block:The body is not a JSON error envelope, so
providerError()indsh-llm-deepseekfinds noerror.messageand falls back to the generic text; status 400 then maps toINVALID_REQUEST.Root cause (both still present in 0.2.0-rc.2)
deepseek-officialwire path. Message text is passed through verbatim andJSON.stringifyemits the lone surrogate as a\udXXXescape — syntactically valid JSON, semantically invalid Unicode. This matches the cross-route asymmetry noted in HTTP 400 (INVALID_REQUEST) on every turn after a tool result contains lone UTF-16 surrogates #3315: the shippedpi-aiadapters callsanitizeSurrogates(), while thedsh-llm-deepseekMessages path has no sanitizer call site (this build's bundle contains the sanitizer only under pi-ai modules).!response.okalready captures the rawtext, but only ascause; the surfacedLlmErrorkeeps the fallback message. A single line of that body would have named the exact problem.Repair path used (for sessions already poisoned)
The poisoned event is durable, so the options are to sanitize at the wire or repair the log. I repaired the log offline:
dsh-session-persistence-jsonl/zstd(435 frames);\ud83descape with a parity-aware scan (so\\ud83dtext does not match); replace it with\ud83d\udd34, restoring the original character (🔴) rather than U+FFFD;ZSTD_c_checksumFlag = 1; all other frames stay byte-identical; no seq renumbering;JSON.parse,seqmonotonic (last = 782), exactly 1 line differs. I could not run the in-app gates offline (assertReleasedV4Relationships/Session.fromRestore), but the file is structurally unchanged.After an app restart the session resumes normally. Two caveats for other users: the default Fork session action is not a workaround because the omitted-
atSeqdefault selects the latest completed-turn prefix and therefore copies the poisoned event; and replacing with U+FFFD is an equally valid repair when restoring the character is not possible.Asks
String.prototype.toWellFormed()over the payload right beforeJSON.stringify— into thedsh-llm-deepseekserialization boundary. Besides covering every producer (model-authored code, tool output, external logs), per the latest comment in [BUG] 工具输出含未配对 UTF-16 代理项会让整个会话永久 HTTP 400(错误信息无法诊断) #8352 this also makes already-poisoned sessions recover on the next request without touching the log.JSON.parse(text)fails for a non-2xx response, include a bounded prefix oftextin the surfaced error, sounexpected end of hex escape(and nginx/413-style bodies, cf. [Bug follow-up] HTTP 413 from nginx surfaced as INVALID_REQUEST — total request-byte guard and safe session recovery #10222) stop hiding behindINVALID_REQUEST.All reactions