Repository navigation
[Bug] 0.2.1-rc.2:permission-presets × auto-review 环形死锁导致 permissions 投影永久丢失(附 32 键判据与可复现证据) #10233
Replies: 2 comments
What this addsThank you for this — the portable criterion (the only missing projection is This reply adds one source-level distinction that changes two of your five requests, and confirms the other two at the line. Everything below is read off 1. The deadlock is not the
|
|
Thanks — this is the most useful reply we could have hoped for, and it corrects us on the two points where we were wrong. We located the ring in the wiring. We had read permission-presets → auto-review as a declared cycle; it is not one. permission-presets injects only The throw is not the defect, the call site is. Reject-before-publication is a property worth keeping and we should not have asked for it to be removed wholesale. Your shape — Request (2): withdrawn. Agreed that making auto-review's dependency optional only relocates the crash to its first use while leaving The unguarded On (3): your point that the swallowed rejection is the price of the veto, and that a fix has to give a failed row a state of its own before the veto is returned rather than after the promise rejects, changes how we would have written that request. Noted, and we will not restate it as "stop swallowing". On (5): "never flushed, only capped" is worse than what we reported, and we did not have it. Never writing the buffer on a clean exit means the diagnostics are lost on exactly the runs where they would matter. And thank you for the explicit negative result on extensions — that the constructor sweep is core-only, and that permission-restore-guard repairs at publication time while our replay strands the session afterwards. We would otherwise have gone looking for a plugin workaround and wasted the time. Yes please: turn §2 into a patch against |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
一、摘要
0.2.1-rc.2 上,会话一旦进入「工具全链失活」状态(
cannot get required service "sessionProjections" in inactive context),重启宿主只能暂时缓解,无法根治。本文不复述 #9812 / #10007 / #10040 / #10041 已有的机制分析,而是补充三样它们没有的东西:permissions」在本机复现,并且区分出了哪些键数差异是正常的、哪些才是本病(见第三节,避免误判)。/permission auto到首个工具失败,间隔 8 秒,全过程无turn/end、无 dispose(见第四节)。disabled: true关掉 auto-review,使pinInitialPermission永远不进入preset === 'auto'分支,故障前提消失(见第六节)。没有修复,只有定位与规避。 版本核查见第二节:rc.2 之后上游零提交,master 上这三处仍在。
二、版本核查(2026-10-19)
dsh-v0.2.1-rc.2,2026-10-17T08:16:33Z,prerelease: true687f282e8,2026-10-17T07:22:43Z(Merge pull request #6408 … release-dsh-0.2.1-rc.2)pushed_at/dsh/package.json@deepseek-ai/dsh-desktop-runtime 0.2.1-rc.2发布序列:rc.2 / rc.1 / alpha.5 / alpha.4 / alpha.3 / alpha.2 —— 全部 prerelease,无 stable。
结论:无可更新版本。以下问题在 master(
687f282)上依然存在。三、判据:怎么确认「我中招了」
3.1 缓存位置与结构
{"record":{"rows":{…}}},rows的键名即投影名。3.2 检测脚本(Node)
3.3⚠️ 关键:不能只看「键数少了」
本机 38 个缓存文件的键数分布是 33×16 / 32×1 / 31×9 / 27×10 / 26×1 / 25×1。
如果按「比最大值少就是坏」去判断,会误伤 20 个正常文件。实测差异如下:
permissionsdsh-memoir/activity、mnemonSubagentTokenUsageclearaicheckpointsagentTeamcostUsagemnemonSubagentTokenUsagedsh-memoir/activity判据必须精确到「缺的是不是
permissions」。 这也解释了为什么 #10040 说「正常 28、故障 27」,而本机是「正常 33、故障 32」——同一个病,插件组合不同,绝对值不可移植,只有「唯一缺失项为 permissions」这个相对判据可移植。3.4 本机实测
session-4329d18b-…session-4395e445-…session-a75a55d3-…session-6858a321-…全库 38 个文件中唯一掉键的就是
4329d18b,时刻落在当晚故障窗口(00:32)内。该会话的
session.v4.jsonl.zstd只有 1 行 / 197 字节,无法从日志侧还原,只能靠 projcache 时间戳定性。四、逐事件回放(
session-6858a321,最干净的样本)关键点:
permission/preset到首个失败仅 8 秒,中间没有任何turn/end、没有 dispose、没有config-reload事件。turn/end仍是completed,不是disposed—— 与 [Bug] Live sessions become permanently tool-dead and lose the permission picker: cannot get required service "sessionProjections" in inactive context, turn/end still reports completed #9812 / [Bug]回合运行中改模型/思维强度/权限,会让会话所有工具调用失效(sessionProjections in inactive context) #10007 一致。run_code外层能跑,只有它内部的tools.*全死(PTC 桥不解析插件服务)。三次故障会话的共同先决动作
session-4395e445/permission auto;12:36:13 切模型 +reasoningEffort: highsession-a75a55d3/permission auto;前 45.9 秒切模型session-6858a321/permission auto三个故障会话全部执行过
/permission auto。这与 #10040 的「preset==='auto'是必要条件」一致,也与 #10007 的「改模型/改强度也会触发」不矛盾 —— 二者都是「运行中重建 agent scope」的不同入口,而auto是最容易踩且后果最重的那一个。一个反向观察(供维护者参考)
本机
session-a75a55d3的 9 个故障段里,有 3 段(19:01 / 21:13 / 08:34)前 10 分钟内没有任何 profile 配置文件改动,而另外几段(22:26 / 22:50 / 23:19 / 00:41)前后有大量.bak写入。→ 写配置文件既非必要也非充分条件,这一点与 #9812 里作者的对照实验(故意写两次 patch 未复现)相互印证。真正必要的是「运行中重建 agent scope」,配置文件只是其中一个入口。
五、机制(复述 #10040 并补充两条我们验证到的细节)
机制更正(2026-10-19,据 @argszero 的源码级复核)
本文初版把这条链描述成「permission-presets ↔ auto-review 环形依赖」。这是错的,特此更正。
permission-presets的 inject 只有['shell', 'approval', 'sessions', 'sessionProjections'](src/index.ts:201),它完全不依赖 auto-review。auto-review在src/index.ts:127声明inject = [... 'permissionPresets' ...],是硬依赖。registerAuto()(:721)永远不跑 →autoAdmit永远undefined→ 那个 throw 的条件永远不清除。真正的问题不是「抛不抛」,而是「从哪个调用点抛」。 同一个 throw 有两个调用点,炸伤半径完全不同:
session/created:245-247packages/core/session/src/index.ts:45明确「同步 throw 会否决并以配对 disposal 回滚」→ 会话被拒绝,服务本身无恙。这是有意设计,tests/permission-presets.spec.ts:300专门保护它ctx.sessions.list():248-250permissions投影这也解释了本文第四节反复出现的「重启好像能修好」:冷启动时
list()为空,走不到调用点 B。真正致命的是「存在活跃 Auto 会话时发生运行时 recompose」。this.autoAdmit唯一赋值点是registerAuto(),全工程唯一调用者是@deepseek-ai/dsh-experimental-auto-review;而 auto-review 又声明对permissionPresets的强依赖,在它构造完成前不会启动。冷启动幸免的原因:
ctx.sessions.list()为空,循环不执行。运行时重载必死的原因:只要内存里存在一个
preset === 'auto'的活跃会话,就必然进入 throw 分支。我们额外验证到的两点:
a75a55d3的故障段「最后一条错误 → 下一条成功工具」间隙实测为 444s / 1044s / 294s / 862s / 136s / 20s / 12s。所以「必须重启」是错误观察,重启只是恰好换了新进程。六、用户侧规避(本机已实施,供参考)
在 patch 层停用 auto-review。
原理:auto-review 不注册,
/permission auto预设就不存在,pinInitialPermission永远不会进入preset === 'auto'分支,那个 throw 不可能被触发。死锁前提消失。代价:失去自动风险审查;
/permission auto从界面消失(这是预期行为)。可正常使用
read-only/workspace-write/danger-full-access。注意:
hmr若开启,改 patch 会立刻 reconcile;建议同时把hmr关掉,改完重启生效,避免「为了修 bug 而触发 bug」。这条规避不能解决的
七、给维护者的具体请求
把 reject 语义按调用点限定(而非全局取消 throw):
session/created路径保留「发布前拒绝」;构造器 sweep(:248-250)改为记为 pending、不抛,待registerAuto()赋完autoAdmit后跑第二次 sweep 补齐。建议形状:pinInitialPermission(session, { reject: boolean }),session/created传true、sweep 传false。这样tests/permission-presets.spec.ts:300仍然为绿。解耦加载时序:auto-review 对permissionPresets的依赖改 optional 或改为服务就绪事件异步注入,消除环形等待。(已撤回) 撤稿理由有两条:①这是单向硬依赖、不是注入环(见第五节「机制更正」),解耦破不了环;②依赖是真实的——auto-review 在
src/index.ts:678读ctx.permissionPresets,在:689/:721/:729-730调registerAuto/current/set。把依赖改成可选只会把崩溃从构造器挪到 auto-review 的首次使用,而autoAdmit依旧永久undefined。失败可见 + 可重试:
vendor/include/src/index.ts:197-200现在把root.update()的 rejection 吞成一条 warn(config update at … failed),被卸载的行永久保持卸载。建议失败行进入可见、可重试的状态。turn 不应比 context 活得久(argszero 在 [Bug]回合运行中改模型/思维强度/权限,会让会话所有工具调用失效(sessionProjections in inactive context) #10007 提的):backing fiber 被 unload 时,turn 应以具名原因结束,而不是报
completed留下僵尸。pinInitialPermission里那处无保护的this.autoAdmit()需要一并加保护::435在没有任何 try 的情况下调用autoAdmit(),而 auto-review 注册的 gate 按设计就会抛(src/index.ts:721-723:if (!accepting) throw new Error('auto-review: integration is closing'))。accepting在:726置 false,与清autoAdmit的:312-315不是同一个 disposer,所以无论两者谁先析构,都存在一段「autoAdmit仍注册但每次调用都抛」的区间。gate 会抛 × pin 无保护地调它 × throw 否决 publication,这个组合本身就是缺陷,与析构顺序无关。希望提供 host stderr 落盘/导出开关:0.2.1-rc.2 桌面版物理上无法拿到
config update at … failed/agent preset <id>: …这几行(%APPDATA%\@deepseek-ai\dsh-desktop\logs\只有crash-<time>-*.log,host stderr 存在 64KB 内存缓冲里,只在崩溃时落盘)。本机与 [Bug]回合运行中改模型/思维强度/权限,会让会话所有工具调用失效(sessionProjections in inactive context) #10007 的另一位报告者独立得到同一结论。若上游需要这三行来定位,目前只能先提供落盘开关。八、我们能提供的
session.v4.jsonl.zstd,多帧 zstd,可按需提供解析脚本)session-4329d18b,32 键)_marks.cjs(提取 model/selection、permission/preset、sandbox/mode 时间线)/_win.cjs(事件窗口回放)/_burst.cjs(故障段聚合)需要哪一份可以直接说。
九、声明
687f282的只读快照,确认 success-only cleanup、Auto bootstrap throw、danger-full-access teardown 三处仍在;这不是对已发布修复的验证。lanslot4n93r的方案时,我们只作引用,未声称其补丁可解决本文全部症状。:435无保护调用)与对 (6) 的补充(buffer 从不 flush)。本机结论(判据、回放、规避)不受影响。附录 A · English Summary(供非中文维护者)
Title:
[Bug] 0.2.1-rc.2 — a permission-presets constructor sweep throw permanently drops thepermissionsprojectionThree findings not covered by #9812 / #10007 / #10040 / #10041:
A portable detection criterion. The absolute key count is plugin-composition dependent ([Bug] 运行时重载导致权限选择器消失与工具永久失效:permission-presets 与 auto 预设的依赖死锁(0.2.1-rc.2 仍复现) #10040 reports 28 normal / 27 broken; our machine reports 33 / 32). The portable form is: the only missing projection is
permissions. On our machine, 31-key files missdsh-memoir/activity+mnemonSubagentTokenUsageand 27-key files miss six unrelated projections — both are healthy. Only the single file at 32 keys missing exactlypermissionsis the bug. Judging by "count went down" produces 20 false positives out of 38.Event-level replay.
/permission autoat 10:16:09.156 → first tool failure at 10:16:25.266 (8s later) → 18 consecutive failures acrossops_bash/compress/read/glob/working_directory/list_agents/job_list/schedule_list/acp_status/get_goal→turn/endreports{"kind":"completed"}→ next turn still dead. Noturn/end, dispose, orconfig-reloadevent between the switch and the first failure.A user-side workaround. Disabling auto-review in the patch layer removes the precondition of the failure entirely, because
pinInitialPermissioncan then never enter thepreset === 'auto'branch that throws. This does not fix the general "mid-turn recompose kills live agents" problem ([Bug]回合运行中改模型/思维强度/权限,会让会话所有工具调用失效(sessionProjections in inactive context) #10007); it only closes the most easily triggered and most damaging entry point.Additional evidence: in the affected session, self-healing was observed at 12s / 20s / 136s / 294s / 444s / 862s / 1044s gaps without any restart — "restart fixes it" is a misreading; the user restarted 8 times and it recurred every time.
Version check: latest release
dsh-v0.2.1-rc.2(2026-10-17T08:16:33Z, prerelease), last commit687f282e8(2026-10-17T07:22:43Z), zero commits since. Verified read-only that the three code paths still exist on687f282; this is not a validation of any shipped fix.Request: (1) scope the reject semantics per call site — keep reject-before-publication on
session/created, record a pending session instead of throwing from the constructor sweep at:248-250, and re-sweep insideregisterAuto(); suggested shapepinInitialPermission(session, { reject }). (2) Withdrawn: this is a one-way hard dependency, not an injection cycle, so decoupling does not break the ring; making it optional only relocates the crash to auto-review's first use. (3) Give a failed row a state of its own beforevendor/includereturns its veto — the swallowed rejection is the price of that veto, so it cannot be fixed after the promise rejects. (4) End the turn with a named reason when its backing fiber unloads. (5) Guard the unguardedthis.autoAdmit()at:435— auto-review's gate throws by design and the same publication veto applies. (6) Expose a host stderr dump/export switch — the desktop build never flushes the 64KB host stderr buffer on a clean exit, so the diagnostics are lost on exactly the runs where they would help.All reactions