You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Same sentence, two opposite answers, and no way to tell which one you are in.
A child session fails with subagent "<id>" is unavailable. There are two sensible reactions — send again, or stop — and the message does not tell you which. In 0.1.7-rc.2 I sent again: delivered, four seconds later. In #10158, tested on 0.2.1-rc.2, the caller sent three times: never delivered, while a fresh child worked immediately.
Both answers are correct, because that one sentence is produced from four different failures. And the count is part of the story: #10158 states that coldResume() has "exactly two" throw sites — the version it tested already had four.
Three versions appear below: I ran 0.1.7-rc.2; I read 0.2.1-rc.2 and 0.2.2-rc.1, and ran neither.
The run in detail. A continuable child's session file is temporarily made unreadable — bytes kept as they were, hash unchanged on restore (I have no reading of that hash, so take that as the operator's statement). The next delivery fails:
subagent "<id>" is unavailable
Restore the file and send again immediately: message delivered to agent <id>, four seconds after the failure. The sentence was not final.
I should say plainly that this is not a novel experiment: @Illusory-moon did the same thing in #10158 with an exclusive handle instead of a rename, and got the same result. Mine adds a second data point to his, not a new method.
Four branches, one code
coldResume produces NOT_RESUMABLE from four places in 0.2.2-rc.1 (three in 0.1.7-rc.2; the fourth was added in between):
branch
what fails
message
retry?
A read the session
observeSession throws anything
subagent "<id>" is unavailable
✅ probably yes — the cause can disappear
B materialize
the materialize step throws a non-SubagentError
subagent "<id>" is unavailable
⚠️ probably not — but see below
C genuinely not resumable
descriptor missing / not continuable
subagent "<id>" has no supported continuation state and cannot be resumed; choose a different target
❌ no — and it says so
D preset projection missing
independent preset selection, but the projection is not a string
subagent "<id>" has no agent preset projection for restoration
❌ no — and it says so
All four carry the same code. A and B share a message; C and D each explain themselves.
One caution on the table, because I do not want to repeat the mistake I am describing: B is a catch-all (!(error instanceof SubagentError)), so it can also receive transient failures. Its "probably not" is a probability, not a rule — and that is exactly the kind of cell this code is unable to fill in.
(Scope: this counts only the paths that produce NOT_RESUMABLE. The same function has other throws — requireSessionQuery, assertAdmitting, authorizeLineage, submitMaterialized — which do not carry this code.)
The strongest evidence is inside the old report
#10158's Environment section says @deepseek-ai/dsh-subagent0.2.1-rc.2. Its body states that coldResume() has "exactly two" throw sites.
That version already had four. I unpacked it and counted: the descriptor check and the preset-projection check are both there, next to the two catch-alls.
This is not carelessness. The reporter gave line numbers, cited the TypeScript sources, and followed the message into the control layer. He read the code closely enough to correct other people. And still the count came out at two — because two of the four never announce themselves as a separate thing: they are exceptions that happened to land in a catch.
A single code with two messages will do that to any reader. It did it to the person writing the report, and it would do it to me.
And the last tool disappears one hop later
In-process, a caller can at least read the message: C and D describe their condition, A and B do not.
Across the process boundary even that is gone. The consumer maps every one of them to a single subagent/not-resumable / subagent cannot be resumed, and the details carry only the child session id — no field for the reason at all.
So the only instrument that distinguished anything was the free-text message, and the free-text message does not survive the hop.
The same move elsewhere
This is a post rather than a comment because the shape repeats — each time, a claim that does not constrain what happens next:
A name that does not name the thing.TRANSPORT (#12823) is used for a missing local attachment object; the user goes looking at their network. In our own #11619 thread, two phrases came up around the same class, and they should be kept apart — conflicting persisted histories and undecodable bytes — because the class name SessionPersistenceCorruptionError reads as the second while the behaviour is the first. The name is the cheapest place to be wrong and the most expensive place to be believed.
A message without a criterion. One string, PTY shell exited during startup, covers the minimal preset (#12456), workspace-write / read-only on Windows (#12115), and sandbox or ACL-restricted setups (#8322). The reporter of #12456 spent the afternoon installing shells.
A claim with no line to an execution point.#12452: the README states that a system prompt at surface node 0 is never shadowed, while the writing path performs no such check — the log the package refuses to load was written by the package.
A failure wearing the shape of success.#12819: retryableCodesreplaces the default table rather than extending it, so writing one code silently removes RATE_LIMIT and TIMEOUT from the retryable set. The setting applies; nothing reports anything.
What they share is not carelessness. It is that a name, a message, a config key and a return value are all claims about what happens next — and nothing checks the claim against the execution. On the happy path each one holds, and the wrong answer arrives shaped like the right one.
Two questions
After is unavailable, should the caller retry or re-route? If both answers are correct depending on which branch spoke, what is the code telling anyone?
And a related one, from a case where the intent was written down: a config key that is accepted, documented, and read by nothing — thresholdRatio, the headroom and per-model policy in cordis.patch.yml, all with no effect because the entry being patched points at a disabled id (#12776). Is that a bug in the key, or a missing line between the key and the thing it names?
Authorship note: the run above was reported by a colleague and read in full; #10158 was read in full, body and both of its comments. The branch table was read from the published 0.1.7-rc.2 bundle and from 0.2.2-rc.1 – read, not run. The counts for 0.2.1-rc.2 come from unpacking that version. Root-cause reading and drafting assisted by AI; verification and publication by me. I have no engineering background — if any technical claim reads wrong, please call it out and I will re-verify and correct.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
EN / English
Same sentence, two opposite answers, and no way to tell which one you are in.
A child session fails with
subagent "<id>" is unavailable. There are two sensible reactions — send again, or stop — and the message does not tell you which. In0.1.7-rc.2I sent again: delivered, four seconds later. In#10158, tested on0.2.1-rc.2, the caller sent three times: never delivered, while a fresh child worked immediately.Both answers are correct, because that one sentence is produced from four different failures. And the count is part of the story:
#10158states thatcoldResume()has "exactly two" throw sites — the version it tested already had four.Three versions appear below: I ran
0.1.7-rc.2; I read0.2.1-rc.2and0.2.2-rc.1, and ran neither.The run in detail. A continuable child's session file is temporarily made unreadable — bytes kept as they were, hash unchanged on restore (I have no reading of that hash, so take that as the operator's statement). The next delivery fails:
Restore the file and send again immediately:
message delivered to agent <id>, four seconds after the failure. The sentence was not final.I should say plainly that this is not a novel experiment:
@Illusory-moondid the same thing in#10158with an exclusive handle instead of a rename, and got the same result. Mine adds a second data point to his, not a new method.Four branches, one code
coldResumeproducesNOT_RESUMABLEfrom four places in0.2.2-rc.1(three in0.1.7-rc.2; the fourth was added in between):observeSessionthrows anythingsubagent "<id>" is unavailableSubagentErrorsubagent "<id>" is unavailablesubagent "<id>" has no supported continuation state and cannot be resumed; choose a different targetsubagent "<id>" has no agent preset projection for restorationAll four carry the same code. A and B share a message; C and D each explain themselves.
One caution on the table, because I do not want to repeat the mistake I am describing: B is a catch-all (
!(error instanceof SubagentError)), so it can also receive transient failures. Its "probably not" is a probability, not a rule — and that is exactly the kind of cell this code is unable to fill in.(Scope: this counts only the paths that produce
NOT_RESUMABLE. The same function has other throws —requireSessionQuery,assertAdmitting,authorizeLineage,submitMaterialized— which do not carry this code.)The strongest evidence is inside the old report
#10158's Environment section says@deepseek-ai/dsh-subagent0.2.1-rc.2. Its body states thatcoldResume()has "exactly two" throw sites.That version already had four. I unpacked it and counted: the descriptor check and the preset-projection check are both there, next to the two catch-alls.
This is not carelessness. The reporter gave line numbers, cited the TypeScript sources, and followed the message into the control layer. He read the code closely enough to correct other people. And still the count came out at two — because two of the four never announce themselves as a separate thing: they are exceptions that happened to land in a
catch.A single code with two messages will do that to any reader. It did it to the person writing the report, and it would do it to me.
And the last tool disappears one hop later
In-process, a caller can at least read the message: C and D describe their condition, A and B do not.
Across the process boundary even that is gone. The consumer maps every one of them to a single
subagent/not-resumable/subagent cannot be resumed, and the details carry only the child session id — no field for the reason at all.So the only instrument that distinguished anything was the free-text message, and the free-text message does not survive the hop.
The same move elsewhere
This is a post rather than a comment because the shape repeats — each time, a claim that does not constrain what happens next:
TRANSPORT(#12823) is used for a missing local attachment object; the user goes looking at their network. In our own#11619thread, two phrases came up around the same class, and they should be kept apart — conflicting persisted histories and undecodable bytes — because the class nameSessionPersistenceCorruptionErrorreads as the second while the behaviour is the first. The name is the cheapest place to be wrong and the most expensive place to be believed.PTY shell exited during startup, covers the minimal preset (#12456),workspace-write/read-onlyon Windows (#12115), and sandbox or ACL-restricted setups (#8322). The reporter of#12456spent the afternoon installing shells.#12452: the README states that a system prompt at surface node 0 is never shadowed, while the writing path performs no such check — the log the package refuses to load was written by the package.#12819:retryableCodesreplaces the default table rather than extending it, so writing one code silently removesRATE_LIMITandTIMEOUTfrom the retryable set. The setting applies; nothing reports anything.What they share is not carelessness. It is that a name, a message, a config key and a return value are all claims about what happens next — and nothing checks the claim against the execution. On the happy path each one holds, and the wrong answer arrives shaped like the right one.
Two questions
Authorship note: the run above was reported by a colleague and read in full;
#10158was read in full, body and both of its comments. The branch table was read from the published0.1.7-rc.2bundle and from0.2.2-rc.1– read, not run. The counts for0.2.1-rc.2come from unpacking that version. Root-cause reading and drafting assisted by AI; verification and publication by me. I have no engineering background — if any technical claim reads wrong, please call it out and I will re-verify and correct.Reported by the OfferKuai Team (Offer快) — Founder: Zhaofeng (Yaming). Website: https://www.offerkuai.com/. Contact: contact@offerkuai.com
中文版
同一句话,两个相反的答案,而你无法知道自己站在哪一边。
一个子会话失败,报
subagent "<id>" is unavailable。合理的反应有两个 —— 再发一次,或者停 —— 而这句话不告诉你是哪一个。在0.1.7-rc.2上,我再发了一次:四秒后送达。在#10158里(测于0.2.1-rc.2),调用方发了三次:一次都没送达,而新建一个立刻可用。两个答案都是对的,因为那一句话由四种不同的失败产出。而「数」本身就是故事的一部分:
#10158说coldResume()有「exactly two」个 throw site —— 而它测的那一版,已经有四个。下文出现三个版本:我跑了
0.1.7-rc.2;读了0.2.1-rc.2与0.2.2-rc.1,两个都没跑。那次运行的细节。 把一个 continuable 子代理的会话文件临时弄成不可读 —— 原字节保留,放回后哈希未变(我没有那个哈希的读数,视为操作者的陈述)。下一次投递失败:
把文件恢复、立刻再发一次:
message delivered to agent <id>,距失败四秒。那句话不是终态。我要直说:这不是新实验。
@Illusory-moon在#10158里做过同一件事(用排他句柄而不是改名),结果一样。我这次只是给他的数据加了一个观察点,不是新方法。四个分支,一个码
在
0.2.2-rc.1里,coldResume有四处会产出NOT_RESUMABLE(0.1.7-rc.2是三处;第四处是中间某个版本加的):observeSession抛任何错subagent "<id>" is unavailableSubagentErrorsubagent "<id>" is unavailablesubagent "<id>" has no supported continuation state and cannot be resumed; choose a different targetsubagent "<id>" has no agent preset projection for restoration四支共用同一个码。 A 与 B 共用一句 message;C 与 D 各自把自己说清了。
关于这张表要加一句谨慎,因为我不想重犯我正在描述的那个错:B 是 catch-all(
!(error instanceof SubagentError)),所以它也可能接住瞬态失败。表里那个「大概不该」是概率,不是规矩 —— 而这一格,恰恰是这个码填不了的那一格。(范围说明:这里只数会产出
NOT_RESUMABLE的路径。同一函数里还有requireSessionQuery、assertAdmitting、authorizeLineage、submitMaterialized等抛出点,它们不带这个码。)最有力的证据,就在那份旧报告里面
#10158的 Environment 段写着@deepseek-ai/dsh-subagent0.2.1-rc.2。它的正文说coldResume()有「exactly two」个 throw site。那一版已经有四个了。 我把它解包数过:descriptor 检查和 preset 投影检查都在,就在两个 catch-all 旁边。
这不是粗心。报告人给了行号、引了 TypeScript 源码、还把 message 追进了控制层 —— 他读得足够细,细到能纠正别人。而分支数仍然数成了两个。因为四个当中有两个从不单独现身:它们是恰好落进某个
catch的异常。一个码、两句话,对任何读者都会这样。它这样对待了写报告的人,也会这样对待我。
而且最后一件工具,过一跳就没了
在进程内,调用方至少还能读 message:C 和 D 描述自己的条件,A 和 B 不描述。
跨过进程边界,连这个也没了。消费点把它们统一映射成
subagent/not-resumable/subagent cannot be resumed,而 details 里只有子会话 id —— 连一个放原因的字段都没有。所以,唯一能区分任何东西的那件工具,是自由文本 message;而自由文本 message,过不了这一跳。
同一个动作,别处也有
它值得一篇帖而不只是一条评论,是因为这个形状在重复 —— 每一次都是一个不约束后续行为的声称:
TRANSPORT(#12823)被用来表示本地附件对象缺失,用户于是去查网络。在我们自己的#11619线程里,围绕着同一个类出现了两个说法,应当分开放:conflicting persisted histories 与 undecodable bytes —— 类名SessionPersistenceCorruptionError读起来是后者,行为是前者。名字是最便宜的出错处,也是最贵的被相信处。PTY shell exited during startup至少盖住三种互不相干的成因:极简预设(#12456)、Windows 上的workspace-write/read-only(#12115)、沙箱或 ACL 受限(#8322)。#12456的报告人为此装了半下午的 shell。#12452:README 承诺 surface node 0 的 system prompt 永不被 shadow,而写入路径不做这个检查 —— 那个包拒绝加载的日志,正是那个包自己写出来的。#12819:retryableCodes是整体替换默认表而不是追加,写一个码进去,RATE_LIMIT和TIMEOUT就静默地不再可重试。设置生效了,什么都没报。它们的共同点不是疏忽,而是:一个名字、一句话、一个配置键、一个返回值,都是关于「接下来会发生什么」的声称 —— 而没有东西去核对这个声称与执行。 在正常路径上,它们每一条都成立;而错误的答案,形状和正确的那个一样。
两个问题
声明:上面那次运行由同事报出、我已通读原文;
#10158的正文与它的两条评论均已通读。分支表读自已发布的0.1.7-rc.2与0.2.2-rc.1—— 读,未运行。0.2.1-rc.2的计数来自解包该版本。根因阅读与文稿撰写由 AI 协助;核验与发布由我本人负责。我非工程背景 —— 若任何技术表述有误,请直接指出,我会对照工具链重新核实并更正。本报告由 OfferKuai(Offer快)团队 提交 —— 创始人:Zhaofeng(Yaming)。官网:https://www.offerkuai.com/。联系:contact@offerkuai.com
All reactions