Replies: 2 comments
|
补充一个我在 WorkBuddy(CodeBuddy 桌面端)环境里实测到的第二个根因。它和 #860 说的信任围栏 403 是两条独立的路,但症状一样:点「继续」报「暂时无法保存确认状态」,弹窗还关不掉。 现象同源 loopback 直连(不是 iframe、没有跨源)的时候, 根因:环境注入的 safe-delete shim 把 fs.rm 拦了WorkBuddy 桌面端会给每个 node 子进程注入: 这个 shim 把 dsh 的
换句话说,文件明明写进去了,就因为在清理锁文件这一步挂了,整个 mutation 被算成失败。这是 dsh 对锁清理失败处理得太严了。 验证用 curl 直连同源 loopback 复现,绕开 iframe 围栏: curl -s -X POST http://127.0.0.1:8080/api/settings.mutate \
-H "Content-Type: application/json" \
-d '{"type":"client-request","rpcId":"m1","method":"settings.mutate","payload":{"ns":"ui-onboarding","ops":[{"op":"set","path":["welcomeNoticeVersion"],"value":"2026-08-13.1"}]}}'返回就是上面那条 修复 / 绕过启动 dsh 前把这个变量清空就行: # bash
NODE_OPTIONS= dsh web
# PowerShell
$env:NODE_OPTIONS=""; dsh web清掉之后 给维护者的建议
|
|
感谢 @meikongyoumo 补充的第二个根因(safe-delete shim 拦截 fs.rm 导致锁清理失败 → settings.mutate 返回 settings-rejected)。这与原文的信任围栏 403 是两条独立路径、同一症状,补充得很到位——尤其"写入已成功、仅锁清理失败却整体判失败"这一点,是原子写+锁清理把失败面放得过大的独立问题。感谢完整的环境与复现说明。 |
Uh oh!
There was an error while loading. Please reload this page.
摘要
欢迎弹窗(WelcomeNotice)的确认动作走特权 RPC
settings.mutate(ui-onboarding.welcomeNoticeVersion),而settings.*系列被信任围栏钉死在 loopback + 同源(PRIVILEGED_METHODS空信任列表)。一旦浏览器上下文不满足围栏(iframe 嵌入、非 loopback 访问、代理/扩展改写Origin/Host、sec-fetch-site: cross-site),settings.mutate返回 403 →acknowledge()失败 → 弹窗显示"暂时无法保存确认状态,请重试"。而弹窗本身没有任何逃生口:OnboardingModal把onClose设为空函数、headless(无关闭钮)、并把整个应用根inert。于是用户被永久锁死在弹窗里,只能无限点击"继续"重试同一条必失败的 403 请求——这正是 #737 的现象。复现步骤
dsh web(默认监听 127.0.0.1:3080)。根因(源码定位,rc.6)
dsh-client-ui-settings-models/lib/client.jsOnboardingModal(L2102-2135):onClose: ignoreImplicitDismiss(空函数,L2094/2119)、headless: true、appRoot.inert = true(L2107)——弹窗无任何关闭途径,且锁死整个应用根。WelcomeNotice(L2252-2299):只有"继续"一个按钮(L2287-2295),点击后
acknowledge();渲染条件(L2267)在status === "error"时仍渲染弹窗。WelcomeNoticeStore.acknowledge()(L2392-2430):host 模式下调用api.settings.mutate(...);response.result.ok为 false 时抛错 → 返回 false→
state.error置位(L2423-2427)→ 界面显示welcomeError("暂时无法保存确认状态,请重试",L2628)。
dsh-client-connection/lib/index.jsPRIVILEGED_METHODS(L504-520):settings.mutate等特权方法用空信任列表二次校验(L538),非loopback+同源请求一律 403 "forbidden"——围栏本身按设计工作(dsh web 打开后所有 /api/* 请求返回 403(transport failure for /api/host.describe / host.pickDirectory: HTTP 403),curl 直连却是 200 #313 已实测矩阵)。
persistence = connection.isLoopback ? "host" : "memory"(L2687)在页面首次加载时判定;iframe/转发场景下浏览器可能被判为 loopback 走 host 模式,
但请求携带的
Origin/sec-fetch-site不满足围栏 → mutate 403 → 弹窗永远停留在 error 态。memory 模式的本地确认路径(L2394-2401)只在"非 loopback"
判定时生效,救不了上述组合。
建议修复
方案 A(推荐)· 确认失败时本地降级 + 后台补写:
acknowledge()失败时不再阻塞——先在内存/本地标记
acknowledged = true放行弹窗(该声明是信息性的,不承载任何安全语义),随后静默重试一次持久化;彻底失败仅提示"稍后可重试"。
一处修改同时解决"锁死"与"无限重试"。
方案 B · 给弹窗加显式逃生口:
OnboardingModal提供"跳过/稍后"按钮(等价memory 模式确认);或
welcomeError状态时允许 Esc/遮罩关闭。方案 C(诊断)· 暴露真实错误:错误文案直接展示底层原因(如
HTTP 403 (settings 写入仅限本机同源)),而不是笼统的"暂时无法保存"——用户至少能知道是环境问题而非"保存失败"。
影响
面板的场景)首次进入即被弹窗永久锁死,UI 完全不可用,刷新无效
settings.mutate,无限空转交互死锁)
环境
验证材料
围栏 403 → 错误态永久渲染)
Origin ≠ Host、sec-fetch-site: cross-site、Host 非 loopback均复现 403First analysis of discussion #737. Happy to open a PR with fix option A.
All reactions