Repository navigation
[BUG] Windows workspace-write:ops_bash 静默假成功(exit 0、stdout 空、命令未执行) #373
Description
Related: #363(同源 IPC 缺陷,但本例是它的静默变体:不报 fatal error、不返回非零 exit、模型无法察觉失败)
环境
- Windows 11 build 26100, x64
- DSH Desktop 2.0.18,DSH core
0.2.1-rc.2 dsh-ops0.4.1,载荷@dsh-ops/bash-win32-x64@0.2.1msys-2.0.dllsha256 =2a89b7c31b323c42e2fb37900af087aeb27417694e630a210bc75b0369410bc7(与provenance.json一致)- 会话沙箱:
workspace-write
现象
workspace-write 会话下,任意 ops_bash 调用都返回"成功"但实际未执行:
- 返回
exitCode: 0,stdout/stderr均为空 - 命令没有真正运行:
exit 42返回 0sleep 3立即返回(无 3 秒延迟)echo x > probe.txt文件未创建(用宿主 PowerShell 复检确认)
- 加
includeDiagnostics: true也不返回任何 diagnostics 字段
关键区别:#363 报告的是 fatal error - CreateFileMapping ... Win32 error 5 + EXECUTOR_UNAVAILABLE(模型能识别失败);本例完全静默,模型会把未执行的命令当成执行成功,这是更危险的失败模式。
对照实验(同一台机器、同一会话)
| # | 实验 | 结果 |
|---|---|---|
| 1 | 宿主 pwsh 跑命令 |
正常回显 ✅ |
| 2 | ops_bash 跑 echo |
exit 0,输出空 |
| 3 | ops_bash 跑 exit 42 |
返回 0 |
| 4 | ops_bash 跑 sleep 3 |
立即返回 0 |
| 5 | ops_bash 指定简单 ASCII workdir |
同样空输出 |
| 6 | 切换会话到 danger-full-access |
完全正常 ✅ |
| 7 | 宿主 shell 直接调原始 bundled bash(node_modules/@dsh-ops/bash-win32-x64/bin/bash.exe) |
couldn't create signal pipe, Win32 error 5 ❌(符合文档描述的受限令牌限制) |
| 8 | 宿主 shell 直接调打补丁的 bash-ipc 副本(~/.dsh/dsh-ops/bash-ipc/<id>/usr/bin/bash.exe) |
patched-ok,exit=42 正确 ✅ |
第 7、8 条是关键:confinedBash() 产出并校验过的运行时本身可用,手工调用完全正常。所以故障出在 sandbox.confine() → ctx.subprocess.spawn() 这条链上,而不是 IPC 补丁或载荷损坏。
已排除
- 不是 [BUG] Windows 受限令牌下,常驻 MSYS 进程 + 另一次独立 ops_bash 必崩:opsnt.cpp 的 selected_section 未覆盖 Cygwin 每用户共享段 S-<sid>.<n> #363 的残留进程问题:
Get-Process检查无任何bash-ipc/bash/sh/sleep残留进程,依然稳定复现 - 不是路径含特殊字符:workspace 路径为纯 ASCII,且指定其它
workdir结果相同 - 不是 bash 载荷损坏:
~/.dsh/dsh-ops/bash-ipc/<id>/.ops-ipc.json收据完整,generationIntact()校验通过(手工执行成功即为证据) - 不是插件冲突:profile 内无第二个 shell 插件;
dsh-ops ladder显示 bash 唯一解析到 bundled 路径且restricted: yes - 不是审批被拒:
approval.js的拒绝路径会抛APPROVAL_*/EXECUTOR_UNAVAILABLE错误,不会返回空成功 - 不是沙箱拒绝:无
[sandbox: ... denied]标记,且denied判定要求exitCode !== 0
推测的故障点(供参考,未验证)
lib/shells/execution.js:89 的 subprocess.spawn() 与 lib/shells/session-execution.js:39 的 sandbox.confine() 之间。
现象看起来是 handle.done 立即 resolve 成一个空的成功 outcome,导致 settledStream() 读到空 reader、exitCode 落成 0,而 projectResult() 因为 exitCode === 0 判定"正常完成"。也就是说:spawn 实际上没有发生(或立即失败),但失败没有被传播到 value.error。
建议在此处加一个断言:handle.started === false 时不应产出 exitCode: 0 的成功结果,而应返回 EXECUTOR_UNAVAILABLE。即便是未定位的底层原因,这个 fail-closed 检查也能避免模型被假成功误导。
附带发现(独立小问题)
lib/shells/constants.js:44 的 BASH_ARGUMENTS 只有 7 个参数:
['command', 'description', 'script_path', 'workdir', 'timeoutMs', 'graceMs', 'includeDiagnostics']
工具的 parameters.properties 里没有 run_in_background。但 lib/policy.js:94 注入给模型的提示词写着:
ops_bash requires description and returns kind foreground, background or promoted. Foreground has status (completed/failed), exitCode, stdout/stderr and error; background/promoted has jobId for host job_output/job_kill.
这与 schema 不一致(模型会尝试传 run_in_background,但 schema 不接受,且 assertKnownArguments 会拒绝未知参数)。建议核对这两处。
影响
受限模式下 ops_bash 整体不可用,且静默——模型会基于"命令成功"的虚假事实继续推理和改代码,比直接报错危险得多。目前只能靠模型主动怀疑并改用宿主 pwsh 兜底。
Activity
CY020924 commentedon Oct 23, 2026
环境 / Environment
2a89b7c31b323c42e2fb37900af087aeb27417694e630a210bc75b0369410bc7(matches provenance.json)workspace-write; workspaceE:\DSH-WorkSpace复现 / Repro(2026-10-23 19:43–19:50 +08:00)
ops_bashreturnsstatus: completed,exitCode: 0,stdout.text: "\r\n", empty stderr.sleep 2; echo DONE; exit 7returns in 332 ms with exit 0 — the command never ran.touch ops_probe_1.txt && exit 3returns 0, and the file does not exist (verified with host PowerShell).script_path(.sh) behaves the same.printf 'X'andecho A; echo Bboth collapse to a single CRLF.danger-full-access, the same commands work:sleep 1measured 1282 ms, stdout/stderr and exit codes correct.新症状:ops_result.put / New symptom: ops_result.put
ops_result {action:"put", items:[...]}->status: unknown,error.code: RESULT_UNKNOWN,message: "Worker returned invalid JSON; inspect audit records.",effectState: unknown,retry: never.refIdreturned.danger-full-accessit succeeds immediately and returnsref://session-.../result/....另一个沙箱相关副作用 / Another sandbox side effect
ops_write_file(ops_probe.sh) carriesMandatory Label\Low Mandatory Level:(I)(NW); the hostpwshRemove-Itemfails with access denied, whileops_removesucceeds. Not observed underdanger-full-access.日志 / Log lines
不确定这两条是否与 #373 相关,供参考。 / Unsure whether these relate to #373; included for reference.
结论 / Takeaway
与 #373 完全一致:
workspace-write下ops_bash静默成功但未执行,模型会把失败当成功继续推理。目前唯一可行绕过是切到danger-full-access(同时修复了本例中的ops_result.put)。希望优先加一个 fail-closed 检查:未真正 spawn 时不要返回exitCode: 0。Consistent with #373: under
workspace-write,ops_bashsilently "succeeds" without executing, so the model proceeds on a false success. The only workaround found is switching todanger-full-access(which also fixedops_result.puthere). A fail-closed check — never returnexitCode: 0when the handle never started — would prevent the silent variant.CY020924 commentedon Oct 23, 2026
环境 / Environment
2a89b7c31b323c42e2fb37900af087aeb27417694e630a210bc75b0369410bc7(matches provenance.json)workspace-write; workspaceE:\DSH-WorkSpace复现 / Repro(2026-10-23 19:43–19:50 +08:00)
ops_bashreturnsstatus: completed,exitCode: 0,stdout.text: "\r\n", empty stderr.sleep 2; echo DONE; exit 7returns in 332 ms with exit 0 — the command never ran.touch ops_probe_1.txt && exit 3returns 0, and the file does not exist (verified with host PowerShell).script_path(.sh) behaves the same.printf 'X'andecho A; echo Bboth collapse to a single CRLF.danger-full-access, the same commands work:sleep 1measured 1282 ms, stdout/stderr and exit codes correct.新症状:ops_result.put / New symptom: ops_result.put
ops_result {action:"put", items:[...]}->status: unknown,error.code: RESULT_UNKNOWN,message: "Worker returned invalid JSON; inspect audit records.",effectState: unknown,retry: never.refIdreturned.danger-full-accessit succeeds immediately and returnsref://session-.../result/....另一个沙箱相关副作用 / Another sandbox side effect
ops_write_file(ops_probe.sh) carriesMandatory Label\Low Mandatory Level:(I)(NW); the hostpwshRemove-Itemfails with access denied, whileops_removesucceeds. Not observed underdanger-full-access.日志 / Log lines
不确定这两条是否与 #373 相关,供参考。 / Unsure whether these relate to #373; included for reference.
结论 / Takeaway
与 #373 完全一致:
workspace-write下ops_bash静默成功但未执行,模型会把失败当成功继续推理。目前唯一可行绕过是切到danger-full-access(同时修复了本例中的ops_result.put)。希望优先加一个 fail-closed 检查:未真正 spawn 时不要返回exitCode: 0。Consistent with #373: under
workspace-write,ops_bashsilently "succeeds" without executing, so the model proceeds on a false success. The only workaround found is switching todanger-full-access(which also fixedops_result.puthere). A fail-closed check — never returnexitCode: 0when the handle never started — would prevent the silent variant.Haoming9527 commentedon Oct 24, 2026
The current implementation was checked against main
662a388and the pinned host subprocess contract.startBashExecutioncopies the explicithandle.doneexit facts, andwithBashStatusrequires exactlyexitCode === 0with no error, permission failure or restriction. There is no fallback that converts a missing exit status to zero. The supportedSubprocessHandlehas nostartedfield; adding that proposed check would reject valid handles rather than establish that Bash executed the command.The exact Desktop empty-success case has not been reproduced in this investigation, so no false-success fix is claimed. The provider contract says
donereports the spawned command's outcome and rejects spawn/provider failures. If a wrapper returns zero without executing the target, the needed evidence is at that host subprocess/sandbox boundary: resolved argv, resolved policy, executable path, startup stderr and the raw completion outcome, compared with the same minimal command outside OPS. Empty output alone cannot distinguish the failure from a valid successful command. An output marker without a trusted protocol would also change or weaken the command contract.The independently reproduced shared-section failure has a narrow fix in #389 for #363. Runtime-directory access is a separate boundary described in #374. The supplemental
run_in_backgroundobservation used the internal legacy definition; the host-facing projection already declares that argument when jobs are available.Closing this repository issue as not planned at the plugin layer, with the observations retained for a host-provider investigation. This is not a claim that the reported Desktop false success is fixed or that the earlier supported-host reproduction is invalid.