Repository navigation
Replies: 1 comment
|
这条全链逐步证实,你的自测量与源码语义完全自洽。补三样:为什么连 toast 都没有是必然、第二处独立缺陷、以及一个你把两个独立缺陷合并成一处的判断(拆开后修法不同)。 1. 缺陷链:六个环节,逐环节有据
零命中的交叉复核: 2. 为什么「无任何 toast」是必然,不是偶发
3. 你的对照组也是对的(这点值得保留)
⇒ 所以"兄弟包都做了、只有 native-command 没做"这个对照成立。 4. ⭐ 第二处独立缺陷:退出码 1 被无条件当成功
await run('explorer.exe', args, signal, 'visible')
…
if (!(error instanceof Error) || !('code' in error) || error.code !== 1) throw error注释 而且包自己的 README 已经记录了这一点(
⇒ 建议把它单列为第二个诉求:超时/退出码 1 且无窗口时回退 5. 一处需要拆开的判断你写"机器侧一次性外壳异常,不作为 DSH 缺陷" —— 这个克制是对的,我同意 explorer 卡住本身不是 DSH 的问题。但请把它明确写成:
这样维护者不会把报告读成"用户在抱怨 Windows"。 6. 最小修法(两行位置)
任一失败都会让 7. 你的诊断日志诉求也成立
8. 版本与边界你写
用户侧 workaround:重启 DSH 窗口(你已知);更轻的是重启 |
Uh oh!
There was an error while loading. Please reload this page.
0. English summary
On Windows, the "Show file location" (
path.reveal) action can permanently lock its button with no error message and no way to retry.Chain: renderer gesture (
dsh-client-ui-open-in-app) → session remoteopenWorkspacePath(dsh-api-session-controller) →revealNativePath(dsh-native-command) →execFile('explorer.exe', ['/select,', <file:/// URI>]).execFileis called without atimeoutoption and without any abort signal, so the promise settles only when the child process exits. If the Windows shell stalls while handling a path argument,explorer.exenever exits → the promise never settles → the client'spendingflag never clears → the button staysdisabledforever. There is no timeout anywhere on this path, while sibling packages in the same repository (dsh-host-open-in-app,dsh-workspace-changes,dsh-api-workspace-controller) all useAbortSignal.timeout(...)or awatchMswindow for exactly this reason.Observed on one machine: the same command hung 182 s with 0 windows before an OS reboot, and completed in ~660 ms opening the correct folder after the reboot (same DSH build, no DSH change). The stall is shell-side and reproducible machine-wide (also from a scheduled task outside DSH's process tree), but DSH turns a transient shell hiccup into a permanently dead button.
Requested fix: a wall-clock bound on this wait (translate a timeout into a normal
path.revealErrortoast and reset the button), plus passing a signal from the client RPC.1. 环境
0.2.0-rc.2(build commit04f392c9ddd144fa426da2045178797da6db6c11)app.asar打包的 dsh runtime)10.0.26100.9445,x64dsh-client-ui-open-in-app、dsh-api-session-controller、dsh-native-command、dsh-client-ui-deliverables(均0.2.0-rc.2)其他说明(可能相关,不作为结论):出问题那次系统已连续开机约 17.5 小时,
explorer.exe自开机后未重启过;系统中注册了若干第三方外壳扩展(HKCR\Directory\shell下的自定义动词),且HKCU\...\Policies\Explorer\EnableShellExecuteHooks = 1。2. 症状(三步复现)
.docx)。实际结果(三个症状同时出现):
data-state="busy"),此后一直灰色,反复点击无效;只能重新加载/重启整个 DSH 窗口才能恢复。期望结果:打开资源管理器并选中该文件;或失败时给出「无法显示文件位置,请重试」,并让按钮回到可点击状态。
3. 代码路径与关键行(
0.2.0-rc.2,均为app.asar内路径)3.1 渲染层:按钮灰态 =
pending永不复位dsh-client-ui-open-in-app/lib/client.js(手势钩子useOpenTargetGesture):同文件里按钮的禁用条件与灰态标记:
结论:
disabled里的busy/loading是外部传进来的(打开方式、预览加载),用户点击时不会变化;点击后唯一会变的就是pending。因此"点完变灰、永远灰"只可能由execute(operation)返回的 promise 永不 settle 造成——若它 reject 或返回{ ok: false },会走.then弹出「无法显示文件位置,请重试」并在.finally复位按钮,这与"无提示 + 永久灰"不符。3.2 会话 RPC:
openPath既无超时也无取消信号这里调用
openWorkspacePath不传任何 signal,也没有AbortSignal.timeout。只要宿主端不返回,这个await就永远挂着,pending永远是true。(对照:dsh-client-ui-deliverables/lib/index.js的服务端路由是把request.signal传下去的,说明链路本身支持取消,只是这条 UI 路径没有用。)3.3 宿主:
execFile('explorer.exe', …)没有超时dsh-api-session-controller/lib/index.js:dsh-native-command/lib/types/path-opener.js(Windows 分支):dsh-native-command/lib/types/runner.js(全文 30 行的核心):注意:
execFile的 options 里没有timeout,resolve/reject只发生在子进程退出之后。也就是说:只要explorer.exe不退出,这条链上没有任何一层会醒过来——没有超时、没有兜底、没有日志。3.4 同一仓库里已有的正确做法(可作修复参考)
dsh-host-open-in-app/lib/index.js:「打开方式 → 文件资源管理器」走launch { kind: 'shell-open' }→runShellOpen(path, watchMs, internals),用 watch 计时窗口判定,超时即resolve("launched")——所以这条路不会把按钮卡死。lib/types/resolver.js:await internals.run(command, args, AbortSignal.timeout(timeoutMs), 'hidden')。dsh-workspace-changes/lib/types/git.js:AbortSignal.any([options.signal, AbortSignal.timeout(this.limits.timeoutMs)])。dsh-api-workspace-controller/lib/index.js:AbortSignal.any([signal, AbortSignal.timeout(this.config.documentsLookupTimeoutMs)])。对
app.asar内 9934 个脚本文件做过全局检索:@deepseek-ai/dsh-native-command与@deepseek-ai/dsh-api-session-controller是唯二完全没有出现任何AbortSignal.timeout/ 超时配置的相关包,而「显示文件位置」正好只走这两个包。另外,
dsh-native-command/README.zh.md自己已经记录了同类风险:即:作者知道"这段等待可能挂死",也设计了 abort 入口,但当前这条 UI 路径没有把 abort/超时接上。
4. 证据与实测数据
4.1 最小复现(不依赖 GUI)
probe.js—— 执行的命令与 DSH 完全一致,参数从环境变量读入(避免命令行引号干扰):窗口检查:
4.2 同一台机器、同一 DSH 构建,重启前后对照
explorer.exe /select, <file:/// URI>(ASCII 目标,DSH 的真实调用形态).docxexplorer.exe file:///…(无/select,,即"打开"路径)Shell.Application.Explore/Invoke-Item/cmd /c start)三点结论:
/select,形式能正确打开并定位目标文件(含中文、全角括号、空格路径);被 DSH 特意"还原非 ASCII"的写法恰恰是必需的——用百分号编码会打开错误的文件夹。4.3 为什么"变灰后不能再次点击"是必然的
if (inFlight.current) return;挡掉;disabled依赖pending,而pending只在.finally里复位;.finally只在 promise settle 时执行,而这条 promise 依赖宿主返回,宿主依赖execFile回调,回调依赖explorer.exe退出;openWorkspacePath的调用也没有 signal,所以没有任何东西能打断它;f !== null才提示,而挂住时.then根本不会执行。5. 同一路径上的次要缺陷
runExplorer认为"explorer 退出码 1 = 已交给桌面进程"(正常时确实如此,本次实测正常态也是errCode=1)。但在"非交互会话"这类场景里,explorer.exe也会以 1 退出而什么都没打开(dsh-native-command/README.zh.md自己记录了这一点)。此时 DSH 返回成功、不报错,用户看到的就是"点了没反应"——静默失败。sessions/**/session.v4.jsonl.zstd)与logs/中都没有openWorkspacePath相关记录,用户无法自查、也无法提供有效证据。explorer.exe <file:/// URI>命令行路径上(path-opener.js的 win32 分支),所以外壳一卡,这些动作会一起永久卡住;只有dsh-host-open-in-app的"打开方式"因为用了watchMs而幸免。建议修复时一并覆盖。6. 建议的修复
最小改动(解决"永久锁死")
dsh-api-session-controller.openWorkspacePath里用AbortSignal.any([signal, AbortSignal.timeout(pathOpenTimeoutMs)])(默认建议 8–10 s,可配置),超时抛RemoteError;runNativeCommand里给execFile加timeout: pathOpenTimeoutMs(注意与signal组合时的语义)。{ ok: false }→ 显示path.revealError("无法显示文件位置,请重试")→.finally复位按钮,用户可以重试。OpenInAppPathController.openPath也应在 RPC 上带 signal/超时(与dsh-host-open-in-app的watchMs思路一致),避免宿主异常时 UI 层无从取消。更完善(可选)
Invoke-Item <path>/cmd /c start "" <path>(本次排查中,故障态下这两条命令仍能正常打开窗口,而/select,命令行不能),从而在"外壳命令行交接卡住"时依然能打开文件夹。dsh-native-command的文档与path.revealError的可用性说明。7. 本报告的局限
0.2.0-rc.2(Windows 桌面版),未在0.2.1-alpha.2上复测;若该路径的超时逻辑已改动,以最新版本为准。EnableShellExecuteHooks = 1,但本次没有做"禁用它们后复现"的对照实验,不作为结论。/select,挂起期间explorer.exe的线程/句柄快照、重启前后同一条命令的完整计时日志。All reactions